Текст
                    
Владимир Дронов Санкт-Петербург « БХВ-Петербург» 2026
УДК ББК 004.438Python 32.973.26-018.1 Д75 Дронов В. А. Д75 Python. Уроки ДJ1Я начинающих. 432 с.: ил. - (Для начинающих) СПб.: БХВ-Петербург, 2026. - 978-5-9775-2126-О ISBN В книге 22 урока, более 40 практических упражнений и заданий для самостоя­ тельной работы. Описан язык Python: его основы, типы данных, управляющие выражения, функ­ ции, объекты, классы, исключения, модули и пакеты. Рассмотрены более развитые инструменты языка: установка и применение дополнительных библиотек, много­ поточное, многопроцессное и конкурентное программирование, аннотации типов. Рассказано о практическом применении Python и различных библиотек ДJ1Я загруз­ ки данных из Интернета, парсинга веб-страниц, работы с базами данных, програм­ мирования графических и неб-приложений, математических расчетов, вывода гра­ фиков и работы с искусственным интеллектом. Электронный архив на сайте изда­ тельства содержит все примеры из книги. Д7я начинающих программистов УДК ББК 004.438Python 32.973.26-018.1 Группа подготовки издания: Руководитель проекта Евгений Рыбаков Зав.редакцией Люд.wила Гауль Редактор Григорий Доб ин Компьютерная верстка Ольги Сергиенко Дизайн серии Карины Соловьевой Оформление обложки Зои Канторович Подписано в печать 30.12.25. Формат 70х100 1 / 16 . Печать офсетная. Усл. печ. л. 34,83. Тираж 1 ООО экз. Заказ № 16489. "БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20. Отпечатано с готового оригинал-макета ООО "Принт-М", ISBN 978-5-9775-2126-О 142300, М.О., г. Чехов, ул. Полиграфистов, д. © ООО 1 "БХВ", 2026 ©Оформление.ООО "БХВ-Петербург", 2026
Оглавление Предисловие ................................................................................................................... 13 Python? ................................................................................................................ 13 ......................................................................................................................... 14 Использованные программные продукты ................................................................................... 14 Типографские соглашения ............................................................................................................ 15 Почему именно Почему эта книга? 1. ЯЗЫК PYTHON: ЧАСТЬ Урок 1. Введение в Python, ОСНОВНЫЕ ИНСТРУМЕНТЫ .............................. часть 17 1 ............................................................................ 19 Python ............................................................................................... 19 Операторы. Приоритет операторов. Скобки ........................................................................ 20 Функции .................................................................................................................................. 23 Переменные. Присваивание ................................................................................................... 24 Типы данных ........................................................................................................................... 26 1.5 .1. Числа - целые и вещественные ................................................................................. 26 1.5.2. Строки ........................................................................................................................... 27 1.5.3. Списки ........................................................................................................................... 28 1.5.4. Кортежи ......................................................................................................................... 29 1.5.5. Словари ......................................................................................................................... 29 библиотека. Модули. Импорт ......................................................................... 30 Стандартная 1.6. ................................................................................................................... 32 классы и Объекты 1.7. 1.7.1. Введение в объекты и классы ...................................................................................... 32 1.7.2. Объектная природа Python .......................................................................................... 33 1.8. Программные ошибки и сообщения о них ........................................................................... 34 1.9. Что еще следует знать о Python в целом, часть 1................................................................. 35 1.1. 1.2. 1.3. 1.4. 1.5. Интерактивный режим Урок 2.1. 2. Введение в 2.1.1. 2.1.2. 2.2. Выяснение типа данных, к которому относится значение Что ....................................... 38 Преобразование значения в другой тип данных ........................................................ 39 Написание программ на 2.2.1. 2.2.2. 2.2.3. 2.2.4. 2.2.5. 2.2.6. 2.2.7. 2.3. Python, часть 2 ............................................................................ 38 Работа с данными разных типов ............................................................................................ 38 Python ............................................................................................. 39 Ввод данных ................................................................................................................. .40 Вывод данных .............................................................................................................. .40 Написание программ в среде IDLE Shell ................................................................... .41 ........................................ .44 Комментарии ............................................................................................................... .45 Правила набора программного кода на Python ......................................................... .45 Сообщения об ошибках в коде программ ................................................................. .47 еще следует знать о Python в целом, часть 2 ................................................................ .47 Альтернативные инструменты для написания программ
Оглавление 4 Урок 3. Логический тип данных, управляющие выражения и блоки ............... .49 3.1. Логический тип данных ........................................................................................................ .49 3 .1.1. Введение в логический тип. Операторы сравнения. Условия ................................. .49 3.1.2. Логические операторы ................................................................................................. 50 3.2. Выражение ветвления ............................................................................................................ 51 3.3. Блоки. Пустой оператор ......................................................................................................... 53 3.3.1. Как набираются выражения, содержащие блоки? ..................................................... 54 3.4. Выражение выбора ................................................................................................................. 55 3.5. Циклы. Цикл с условием ........................................................................................................ 56 3.6. Управление циклами .............................................................................................................. 58 3.6.1. Переход на следующую итерацию ............................................................................. 58 3.6.2. Прерывание цикла ........................................................................................................ 58 3.7. Автоматическое преобразование типов в условиях управляющих выражений ............... 59 3.8. Оператор присваивания в составе выражения ..................................................................... 59 3.9. Что еще следует знать о блоках и не только ........................................................................ 60 3.1 О. Самостоятельные упражнения ............................................................................................ 61 Урок 4.1. 4. Числа и строки Числа 4.1.l. 4.1.2. 4. 1.3. 4.2. 4.3. 4.4. 4.5. ................................................................................................ 62 целые и вещественные ........................................................................................... 62 Запись чисел .................................................................................................................. 62 Преобразование значений в числа .............................................................................. 63 ........................................................................................................... 64 4.1.3.1. Арифметические операции ............................................................................. 64 4.1.3.2. Алгебраические и тригонометрические операции ....................................... 65 4.1.3.3. Генерирование псевдослучайных чисел ....................................................... 66 4.1.4. Сравнение чисел ........................................................................................................... 67 4.1.5. Потери точности при вычислении .............................................................................. 68 Строки ...................................................................................................................................... 68 4.2.1. Создание строк ............................................................................................................. 68 4.2.1.1. Специальные символы .................................................................................... 69 4.2.2. Преобразование значений в строки ............................................................................ 70 4.2.3. Обработка строк ........................................................................................................... 70 4.2.3.1. Извлечение символов, срезов и перебор. Цикл перебора ............................ 70 4.2.3.2. Простейшие операции над строками ............................................................. 72 4.2.3.3. Преобразование строк ..................................................................................... 72 4.2.3.4. Поиск и замена в строках ............................................................................... 74 4.2.4. Сравнение строк ........................................................................................................... 75 4.2.5. Форматируемые строки ............................................................................................... 75 Последовательности ............................................................................................................... 78 Что еще нужно знать о строках и не только ......................................................................... 79 Самостоятельные упражнения .............................................................................................. 81 Урок 5.1. - 5. Обработка чисел Списки, кортежи, генераторы и словари Списки ................................................... 82 ..................................................................................................................................... 82 5.1.1. Создание списков ......................................................................................................... 82 5.1.2. Обработка списков ....................................................................................................... 82 5.1.2.1. Простейшие операции над списками ............................................................ 82 5.1.2.2. Добавление и удаление элементов списка .................................................... 84 5.1.2.3. Обработка элементов списка .......................................................................... 85 5.1.2.4. Поиск элементов в списках ............................................................................ 86 5.1.2.5. Выдача элементов списков, выбранных псевдослучайным образом ......... 87
5 Оглавление ....................................................................................................... 88 ................................................................................................. 88 ............................................................................... 89 .................................................... 5.2. Кортежи 5.2.1. Создание кортежей ....................................................................................................... 89 5.2.2. Работа с кортежами ...................................................................................................... 89 5.2.3. Именованные кортежи ................................................................................................. 90 5 .3. Распаковка последовательностей .......................................................................................... 91 5.3.1. Пара слов о множественном присваивании и множественном выводе ................... 93 5.4. Генераторы .............................................................................................................................. 93 5.4.1. Генераторы-функции ................................................................................................... 93 5.4.2. Генераторы-выражения ................................................................................................ 95 5.5. Словари .................................................................................................................................... 95 5.5.1. Создание словарей ........................................................................................................ 95 5.5.2. Обработка словарей ..................................................................................................... 96 5.5.2.1. Простейшие операции над словарями ........................................................... 96 5.5.2.2. Добавление, извлечение и удаление элементов словаря ............................. 97 5.5.2.3. Перебор элементов словаря ............................................................................ 98 5.5.3. Сравнение словарей ................................................................................................... 100 5.5.4. Словарные включения ............................................................................................... 100 5.5.5. Словари со значением по умолчанию defaultdict .................................................... 101 5.6. Отображения ......................................................................................................................... 101 5.7. Особенности хранения значений в переменных. Ссылки ................................................. 102 5.8. Копирование списков, кортежей и словарей ...................................................................... 103 5.9. Что еще нужно знать о генераторах и не только ............................................................... 104 5.1 О. Самостоятельные упражнения .......................................................................................... 105 Сравнение списков 5.1.3. 5.1.4. Урок 6.1. 6. Списковые включения Функции .......................................................................................................... 106 Вызов функций 6.1.1. 6.1.2. 6.2. ..................................................................................................................... 106 Виды параметров функций ........................................................................................ 106 Распаковка последовательностей и отображений в вызовах функций .................. ) 07 Объявление своих функций ................................................................................................. 108 ................................................................................................. 108 109 Необязательные параметры ....................................................................................... 11 О Произвольное количество параметров ..................................................................... 111 Строго позиционные, строго именованные и двоякие параметры ........................ 112 6.3. Функция как значение .......................................................................................................... 114 6.3.1. Разные функции и методы, принимающие с параметрами другие функции ........ 114 6.4. Лямбда-функции ................................................................................................................... 116 6.5. Вложенные функции ............................................................................................................ 117 6.5.1. Замыкания. Фабрики функций .................................................................................. 118 6.6. Декораторы функций ........................................................................................................... 120 6.7. Сообщения об ошибках в телах функций. Стек вызовов .................................................. 122 6.8. Что еще нужно знать о функциях ........................................................................................ 123 6.9. Самостоятельное упражнение ............................................................................................. 124 6.2.1. 6.2.2. 6.2.3. 6.2.4. 6.2.5. Урок 7.1. 7. Объявление функции Доступ к глобальным переменным из тела функции .............................................. Классы и объекты ......................................................................................... 125 ................................................................................................................................. 125 7.1.1. Создание объектов ..................................................................................................... 125 7.1.2. Обращение к атрибутам и вызов методов ................................................................ 125 7.1.3. Объектные и классовые атрибуты и методы ........................................................... 126 Объекты
Оглавление б ................................................................................................................................... 127 ..................................................................................................... 127 Конструктор и деструктор ......................................................................................... 128 Наследование классов ................................................................................................ 130 7 .2.3 .1. Объявление производного класса ................................................................ 130 7.2.3.2. Перекрытие и переопределение методов .................................................... 131 7.2.3.3. Множественное наследование ..................................................................... 133 7.2.3.4. Указание декораторов функций у методов ................................................. 134 7.2.4. Классовые атрибуты и методы. Статические методы ............................................. 134 7.2.5. Закрытые атрибуты и методы ................................................................................... 135 7.2.6. Свойства ...................................................................................................................... 136 7.2.7. Добавленные атрибуты .............................................................................................. 138 7.2.7.1. Ограничение набора создаваемых атрибутов ............................................. 138 7.2.8. Вложенные классы ..................................................................................................... 139 7.2.9. Класс как значение ..................................................................................................... 140 7.3. Декораторы классов ............................................................................................................. 140 7.4. Что еще нужно знать об объектах и классах ...................................................................... 141 7.5. Самостоятельные упражнения ............................................................................................ 142 7.2. Классы Объявление класса 7.2.1. 7.2.2. 7.2.3. Урок 8. Модули и пакеты .......................................................................................... 143 ........................................................................................ 143 143 8.1.1.1. Импорт сущностей ........................................................................................ 143 8.1.1.2. Подключение модулей .................................................................................. 144 8.1.2. Указание сущностей, доступных извне .................................................................... 145 8.1.3. Создание своих модулей ............................................................................................ 145 8.2. Пакеты ................................................................................................................................... 146 8.2.1. Создание пакетов ........................................................................................................ 146 8.2.2. Импорт и подключение из пакетов. Пути сущностей ............................................. 146 8.3. Что еще нужно знать о модулях, импорте и подключении .............................................. 148 8.4. Самостоятельное упражнение ............................................................................................. 149 8.1. Модули. Импорт и подключение Использование сущностей из другого модуля ......................................................... 8.1.1. Урок 9. Исключения и их обработка. Обработчики контекстов ....................... 150 150 ............................................................................................. 150 Обработка исключений .............................................................................................. 151 Возбуждение исключений ......................................................................................... 153 Объявление своих исключений ................................................................................. 154 9.2. Обработчики контекстов ...................................................................................................... 154 9 .1. Исключения ........................................................................................................................... Введение в исключения 9 .1.1. 9 .1.2. 9 .1.3. 9.1.4. ЧАСТЬ Урок 10.1. 10.2. 11. ЯЗЫК PYTHON: 10. РАСШИРЕННЫЕ ИНСТРУМЕНТЫ ................. Функции и классы особой функциональности 157 ..................................... 159 ........................................................................................ 159 Dunder-мeтoды и их использование .................................................................................. 160 10.2.1. Преобразование объектов в другие типы данных ................................................ 161 10.2.2. Поддержка арифметических операций с объектами. Перегрузка операторов ........................................................................................... 162 10.2.3. Поддержка операций сравнения ............................................................................ 164 10.2.4. Создание своих итераторов ................................................................................... 164 Создание генераторов-функций
7 Оглавление .................................................................. 166 ................................................................................ 167 Прочие dunder-мeтoды ........................................................................................... 168 Написание менеджеров контекста .................................................................................... 170 10.3.1. Функции-менеджеры контекста ............................................................................ 170 10.3.2. Классы-менеджеры контекста ............................................................................... 171 Перечисления ...................................................................................................................... 172 Что еще нужно знать о классах особой функциональности ........................................... 175 Самостоятельные упражнения .......................................................................................... 176 10.2.5. 10.2.6. 10.2.7. 10.3. 10.4. 10.5. 10.6. Урок 11.1. 11.2. Создание своих последовательностей Создание своих отображений 11. Регулярные выражения ............................................................................. 177 Создание регулярных выражений ..................................................................................... 177 Написание регулярных выражений ................................................................................... 177 11.2.1. Метасимволы .......................................................................................................... 178 11.2.2. Поднаборы ............................................................................................................... 178 11.2.3. Вариант .................................................................................................................... 179 11.2.4. Квантификаторы ..................................................................................................... 179 11.2.5. Утверждения ........................................................................................................... 180 11.2.6. Группы ..................................................................................................................... 181 11.2. 7. Обычные символы .................................................................................................. 182 11.2.8. Флаги ....................................................................................................................... 182 11.2.9. Тестирование регулярных выражений .................................................................. 183 11.3. Использование регулярных выражений ........................................................................... 184 11.3 .1. Поиск первого совпадения ..................................................................................... 184 11.3.2. Поиск всех совпадений .......................................................................................... 186 11.3 .3. Прочие случаи использования ............................................................................... 186 11.4. Что еще нужно знать о регулярных выражениях и не только ........................................ 188 11.5. Самостоятельные упражнения .......................................................................................... 188 Урок 12. Установка и использование дополнительных библиотек ................... 189 pip ................................................ 189 Библиотека Pillow: работа с графикой .............................................................................. 191 Программа для создания графических миниатюр, версия 1.0 ........................................ 192 Модуль argparse: обработка командных ключей ............................................................ 194 12.4.1. Введение в командные ключи и их обработка ..................................................... 194 12.4.2. Создание объекта обработчика командных ключей ............................................ 195 12.4.3. Описание командных ключей ................................................................................ 196 12.4.4. Обработка командных ключей .............................................................................. 198 12.5. Программа для создания графических миниатюр, версия 2.0 ........................................ 199 12.6. Что еще нужно знать о дополнительных библиотеках ................................................... 201 12.7. Самостоятельные упражнения .......................................................................................... 202 12.1. 12.2. 12.3. 12.4. Работа с дополнительными библиотеками. Утилита Урок 13 .1. 13. Многопоточное и многопроцессное программирование ..................... 203 Многопоточное программирование .................................................................................. 203 13.1.1. Введение в потоки. Глобальная блокировка интерпретатора ............................. 203 13.1.2. Создание потоков .................................................................................................... 204 13 .1.3. Блокировки .............................................................................................................. 206 13.1.4. Программа для создания графических миниатюр, версия 3.0 ............................ 207 13.1.5. Очереди .................................................................................................................... 209 13 .1.6. Программа для создания графических миниатюр, версия 3 .1 ............................ 211
Оглавление в 13.2. Многопроцессное программирование .............................................................................. 213 ..................................................................................... 213 Программа для создания графических миниатюр, версия 4.0 ............................ 214 13.3. Что еще нужно знать о потоках и процессах ................................................................... 215 13.4. Самостоятельные упражнения .......................................................................................... 216 13.2.1. 13 .2.2. Урок 14.1. 14. Конкурентное программирование ........................................................... 217 Сопрограммы ...................................................................................................................... 217 ..................................................................... 217 Сопрограммы-методы ............................................................................................ 220 14.1.2.1. Асинхронные итераторы. Цикл перебора с приостановкой ................ 220 14.1.2.2. Асинхронные менеджеры контекста. Асинхронный обработчик контекста ..................................................... 221 14.1.3. Сопрограммы-генераторы ...................................................................................... 222 Задачи .................................................................................................................................. 222 14.2.1. Создание и выполнение задач ............................................................................... 222 14.2.2. Одновременный запуск задач ................................................................................ 223 14.2.3. Группы задач ........................................................................................................... 224 14.2.4. Прерывание задач ................................................................................................... 225 Асинхронные очереди ........................................................................................................ 226 Библиотека aiofiles: конкурентная работа с файлами ..................................................... 226 Программа для создания графических миниатюр, версия 5.О ........................................ 227 Самостоятельные упражнения .......................................................................................... 230 14.1.1. 14.1.2. 14.2. 14.3. 14.4. 14.5. 14.6. Урок 15.1. Процессы и работа с ними Введение в сопрограммы. Фьючеры 15. Аннотации типов и документирование кода. Датаклассы ................. 231 Аннотации типов и их применение ................................................................................... 231 15.1.1. Написание аннотаций типов 15.1.1.1. 15.1.1.2. 15.1.1.3. .................................................................................. 231 Указание типов в аннотациях ................................................................. 232 Указание типов элементов у списков и кортежей ................................ 233 Указание типов элементов у последовательностей и отображений произвольных типов ............................................................................... 234 .................. 235 Указание типов у фьючеров ................................................................... 235 Указание типов значений, выдаваемых генераторами-функциями ...... 236 Обобщенные типы ................................................................................... 237 15 .1.2. Получение аннотаций, заданных у функций и методов ...................................... 23 7 Строки документирования ................................................................................................. 238 Датаклассы .......................................................................................................................... 241 15.3.1. Простейшие датаклассы ......................................................................................... 241 15.3.2. Более сложные датаклассы .................................................................................... 243 Что еще нужно знать об аннотациях типов ...................................................................... 245 Самостоятельное упражнение ........................................................................................... 245 15.1.1.4. 15.1.1.5. 15.1.1.6. 15.1.1.7. 15.2. 15.3. 15.4. 15.5. ЧАСТЬ Урок 16.1. 111. 16. Указание типов у функций, присваиваемых переменным ПРАКТИЧЕСКОЕ РУТНОN-ПРОГРАММИРОВАНИЕ ............... 247 Загрузка и анализ данных из Интернета ................................................ 249 Библиотека requests: загрузка файлов из Интернета ....................................................... 249 16.1.1. Requests: отправка клиентских запросов .............................................................. 249 16.1.2. Requests: обработка полученных серверных ответов .......................................... 251 16.1.3. Requests: загрузка больших файлов ...................................................................... 252
9 Оглавление 16.2. Библиотека 16.2.1. 16.2.2. 16.2.3. 16.2.4. 16.2.5. 16.2.6. Beautiful Soup: парсинг веб-страниц ............................................................ 253 Подготовка к парсингу веб-страницы ................................................................... 253 Навигация по структуре узлов неб-страницы ...................................................... 254 Получение сведений об узле .................................................................................. 256 Поиск узлов ............................................................................................................. 257 Программа, выводящая заголовки статей на веб-сайте ЗDNews.ru .................... 262 Программа, сохраняющая изображения с заданной неб-страницы, 1.0 ................................................................................................................. 263 конкурентная загрузка файлов ........................................................ 266 aiohttp: 16.3. 16.3.1. Aiohttp: отправка клиентских запросов ................................................................ 267 16.3.2. Aiohttp: обработка серверных ответов .................................................................. 267 16.3.3. Aiohttp: загрузка больших файлов ........................................................................ 268 16.3.4. Программа, сохраняющая изображения с заданной неб-страницы, версия 2.0 ................................................................................................................. 269 16.4. Самостоятельные упражнения .......................................................................................... 271 версия Библиотека Урок 17. Разработка неб-приложений, часть 1 ...................................................... 272 NiceGUI ............................................................................................ 272 17. 1.1. Страницы и маршруты ........................................................................................... 272 17.1.2. Запуск и остановка неб-приложения ..................................................................... 274 17.1.3. Создание гиперссылок ........................................................................................... 275 17.1.4. Параметризованные маршруты и URL-параметры ............................................. 276 17.1.5. Обработка событий ................................................................................................. 277 17.1.6. Функция run(), запускающая веб-приложение ..................................................... 279 17.2. Основные элементы страниц ............................................................................................. 280 17.2.1. Надпись .................................................................................................................... 280 17 .2.2. Графический элемент ............................................................................................. 280 17.2.3. Гиперссылка ............................................................................................................ 280 17.2.4. Текст в формате Markdown .................................................................................... 281 17.2.5. Аудиоролик ............................................................................................................. 282 17 .2.6. Видеоролик .............................................................................................................. 283 17.2.7. Фрагмент программного кода ................................................................................ 283 17.3. Разметочные элементы ....................................................................................................... 284 17.3.1. Блок-строка ............................................................................................................. 284 17.3.2. Блок-колонка ........................................................................................................... 284 17.3.3. Разделитель ............................................................................................................. 285 17.3.4. Блок-сетка ................................................................................................................ 285 17.3.5. Спойлер ................................................................................................................... 286 17.3.6. Быстрая разметка страниц ..................................................................................... 288 17.4. Если вы знаете HTML и CSS ............................................................................................. 290 17.4.1. Создание произвольных НТМL-элементов .......................................................... 290 17.4.2. Указание стилевых классов и встроенных СSS-стилей ...................................... 291 17.4.3. Обработка событий DOM ...................................................................................... 292 17.5. Статические и выгруженные файлы ................................................................................. 292 17.6. Веб-приложение фотогалереи, версия 1.0 ........................................................................ 293 17.7. Что еще нужно знать о NiceGUI, часть 1.......................................................................... 296 17.8. Самостоятельные упражнения .......................................................................................... 297 17.1. Основы библиотеки Урок 18.1. 18.2. 18. Работа с базами данных ............................................................................. 298 Введение в реляционные базы данных ............................................................................. 298 Библиотека Tortoise ORM: удобная работа с базами данных ......................................... 299
Оглавление 10 18.3. Модели ................................................................................................................................. 299 18.3.1. 18.3.2. 18.3.3. 18.3.4. 18.3 .5. Объявление моделей ............................................................................................... 300 Настройки, поддерживаемые всеми полями ........................................................ 300 Типы полей .............................................................................................................. 301 Создание межтабличных связей ............................................................................ 302 ....................................................................................... 303 ..................................................... 304 Запись данных ..................................................................................................................... 305 18.5.1. Создание записей .................................................................................................... 305 18.5.2. Правка записей ........................................................................................................ 306 18.5.3. Удаление записей .................................................................................................... 307 18.6. Выборка данных ................................................................................................................. 307 18.6.1. Поиск записи ........................................................................................................... 307 18.6.2. Извлечение значений из полей записи .................................................................. 308 18.6.3. Фильтрация записей. Наборы записей .................................................................. 308 18.6.3.1. Модификаторы ........................................................................................ 309 18.6.4. Сортировка записей ................................................................................................ 311 18.6.5. Извлечение первой и последней записей ............................................................. 311 18.6.6. Извлечение заданного количества записей .......................................................... 312 18.6.7. Извлечение всех записей ........................................................................................ 312 18.6.8. Проверка существования записей ......................................................................... 312 18.6.9. Извлечение связанных записей ............................................................................. 313 18.6.10. Извлечение записей в виде списка словарей ...................................................... 314 18. 7. Закрытие соединения с базой данных ............................................................................... 314 18.8. Интеграция Tortoise ОRМ и NiceGUI ............................................................................... 315 18.9. Веб-приложение фотогалереи, версия 2.0 ........................................................................ 315 18. 9 .1. Модели пользователя и изображения ................................................................... 315 18.9.2. База данных веб-приложения ................................................................................ 317 18.9.3. Вывод изображений из базы данных .................................................................... 317 18.1 О. Что еще нужно знать о библиотеке Tortoise ОRМ ......................................................... 319 18.11. Самостоятельные упражнения ........................................................................................ 320 18.4. 18.5. Настройки самой модели Соединение с базой данных и инициализация схемы Урок 19. Разработка веб-прнложеннй, часть 2 ...................................................... 321 ........................................................................................................ 321 19.1.1. Кнопка ...................................................................................................................... 321 19.1.2. Поле ввода ............................................................................................................... 321 19.1.3. Область редактирования ........................................................................................ 322 19.1.4. Флажок ..................................................................................................................... 322 19.1.5. Набор переключателей ........................................................................................... 323 19.1.6. Раскрывающийся список ........................................................................................ 323 19.1.7. Поле для ввода числа .............................................................................................. 325 19.1.8. Регулятор ................................................................................................................. 326 19.1.9. Поле для выбора файлов ........................................................................................ 326 19.2. Хранилища данных NiceGUI ............................................................................................. 328 19.3. Хеширование паролей ........................................................................................................ 329 19.4. Веб-приложение фотогалереи, версия 3.0 ........................................................................ 330 19.4.1. Верхнее меню и секретный ключ .......................................................................... 330 19.4.2. Страницы входа и выхода ...................................................................................... 331 19.4.3. Страница пользовательского профиля .................................................................. 333 19.4.4. Страницы добавления и удаления изображений ................................................. 335 19.1. Элементы управления
11 Оглавление 19.5. 19.6. Самостоятельное Урок 20.1. 20.2. 20. Разработка Библиотека Toga: графических приложений .................................................... 339 разработка графических приложений ................................................ 339 Основы библиотеки Toga ................................................................................................... 339 20.2.1. Окна и контейнеры ................................................................................................. 339 20.2.2. Обработка событий ................................................................................................. 341 20.2.3. Оформление приложения. Стили .......................................................................... 342 20.2.4. 20.3. NiceGUI, часть 2 ..................................................... 338 упражнение ........................................................................................... 338 Что еще нужно знать о библиотеке Запуск и останов приложения ............................................................................... 343 Простейшие элементы окон ............................................................................................... 344 20.3.1. Свойства и методы, поддерживаемые всеми элементами окон ......................... 344 20.3.2. Надпись .................................................................................................................... 345 20.3.3. Поле ввода ............................................................................................................... 345 20.3.4. Область редактирования ........................................................................................ 345 20.3.5. Флажок ..................................................................................................................... 346 20.3.6. Кнопка ...................................................................................................................... 346 20.3.7. Графический элемент ............................................................................................. 347 20.3.8. Поле для ввода целого числа ................................................................................. 347 20.3.9. Поле для ввода пароля ............................................................................................ 348 20 .3 .1 О. Регулятор ............................................................................................................... 348 20.4. Модели и списки ................................................................................................................. 349 20.4.1. Модели ..................................................................................................................... 349 20.4.2. Списковые элементы управления ......................................................................... 351 20.4.2.1. Раскрывающийся список ........................................................................ 351 20.4.2.2. Таблица .................................................................................................... 352 20.5. Контейнеры ......................................................................................................................... 353 20.5.1. Стопка ...................................................................................................................... 353 20.5.2. Блокнот с вкладками .............................................................................................. 354 20.5.3. Панель с прокруткой .............................................................................................. 355 20.6. Задание стилей у элементов окон ...................................................................................... 356 20.7. Управление окнами ............................................................................................................ 358 20.7.1. Задание главного окна приложения ...................................................................... 358 20.7.2. Создание вторичных окон ...................................................................................... 360 20.7.3. Вывод стандартных модальных окон ................................................................... 363 20.8. Интеграция библиотек Toga и Tortoise ОRМ ................................................................... 364 20.9. Что еще нужно знать о библиотеке Toga .......................................................................... 364 20.1 О. Самостоятельные упражнения ........................................................................................ 365 Урок 21. Математика .................................................................................................. 366 ........................................................................... 366 ......................... 366 .................................................... Matplotlib 21.1.1. 21.1.1.1. Вывод простого графика ......................................................................... 366 21.1.1.2. Вывод двух графиков на одних координатных осях ............................ 368 21.1.1.3. Вывод двух графиков на разных координатных осях .......................... 369 21.1.1.4. Настройка графиков ................................................................................ 370 21.1.2. Создание координатных осей ................................................................................ 372 21.1.3. Рисование графиков разных типов ........................................................................ 373 21.1.4. Вывод и оформление графиков ............................................................................. 377 21.2. Библиотека NumPy: обработка многомерных массивов ................................................. 378 21.3. Что еще нужно знать о библиотеках Matplotlib и NumPy ............................................... 382 21.1. Библиотека Matplotlib: вывод графиков Основные принципы
Оглавление 12 Урок 22. Искусственный интеллект ........................................................................ 383 GigaChat АР! и получение ключа авторизации ......... 383 LangChain, langchain-community и \angchain-gigachat ................................ 384 Разработка ИИ-клиентов .................................................................................................... 384 22.3.1. Соединение с большой языковой моделью .......................................................... 384 22.3.2. ИИ-клиент: отправка запросов и получение ответов .......................................... 385 22.3.3. ИИ-клиент: хранение истории взаимодействия с языковой моделью ............... 387 22.3.4. ИИ-клиент: очистка истории ................................................................................. 388 22.3.5. Получение ответа по частям .................................................................................. 390 22.3.6. Конкурентная работа с языковыми моделями ..................................................... 391 22.4. Разработка ИИ-аrентов ...................................................................................................... 392 22.4.1. Объявление инструментов ..................................................................................... 392 22.4.1.1. Инструмент для поиска в Интернете средствами DuckDuckGo .......... 393 22.4.2. Создание ИИ-аrента ............................................................................................... 393 22.4.3. ИИ-агент: отправка запросов и получение ответов ............................................. 394 22.4.4. Обработка ответов .................................................................................................. 396 22.4.5. ИИ-аrент: хранение истории взаимодействия с языковой моделью .................. 397 22.4.6. Работа с состоянием ИИ-аrента ............................................................................. 399 22.4. 7. ИИ-аrент: очистка истории ................................................................................... .400 22.5. Что еще нужно знать о работе с искусственным интеллектом ..................................... .401 22.6. Самостоятельное упражнение ........................................................................................... 402 22.1. 22.2. 22.3. Регистрация в личном кабинете Библиотеки Заключение ................................................................................................................... 403 IIРИЛОЖЕНИЯ ........................................................................................................... 405 Приложение 1. Установка исполняющей среды Приложение 2. Приоритет операторов Приложение 3. Использование ИИ для изучения и написания программного Приложение 4. Python ...................................... 407 Python ....................................................... 411 Python кода .............................................................................. 412 Описание файлового архива ......................................................... 416 Предметный указатель ............................................................................................... 417
Предисловие Язык программирования Python появился в далеком уже году. Долгое время 1991 он существовал, что называется, «где-то там», пребывал на задворках компьютер­ ной индустрии, в то время как бал правили другие языки. И в конце 2000-х годов внезапно вырвался из тени и к настоящему времени прочно утвердился на троне короля языков программирования. Почему именно Python? ♦ Python прост. Его основами можно овладеть буквально за день 1 • ♦ Python ясен. Он манипулирует простыми и понятными типами данных: числами, строками, списками и др. В нем нет никаких чересчур мудреных программных концепций или сверхсложных механизмов. Он не заставляет вас забыть всё, что вы знали о программировании раньше. Он не «ломает» вам мозг. ♦ Python лаконичен. Его языковые конструкции исключительно компактны. Он использует минимум служебных слов и символов. Он предлагает такие про­ граммные инструменты, что программа десятков строк, будучи переписана на ♦ Python - на Python, другом языке, занимающая пару уменьшится вдвое. язык современной науки. Огромное количество программ для научных исследований (в частности, математических), экономических и финансовых рас­ четов сейчас пишется на нем. ♦ Python - язык обработки данных. На нем написано огромное количество про­ грамм для работы с большими массивами текстовой, графической, звуковой и видеоинформации. ♦ Python - язык искусственного интеллекта, одного из величайших технологиче­ ских достижений текущего столетия! Кто знает, насколько позже появилось бы это впечатляющее достижение, если бы не было Мы ?оворим «искусственный интеллект» ♦ Python - - Python ... подразумеваем Python. это перспективно. На нем уже написано и продолжает писаться такое количество программ, в которые вложено столько усилий и денег, что за его будущее можно не беспокоиться. И время, потраченное на изучение этого вели­ колепного языка, очень быстро окупится. Мы юворим «XXI век» - подразумеваем Python. 1 А есни вы 11среходитс на него с других языков 11ро1·раммирования - то и за полчаса.
Предисловие 14 Ну и следует заметить, что на Python написано огромное количество дополнитель­ ных программных библиотек, существенно упрощающих работу программистам. Эти библиотеки позволяют с легкостью загружать и анализировать данные из Ин­ тернета, работать с информационными базами разных форматов, разрабатывать графические и веб-приложения, производить сложнейшие математические расчеты и, да, работать с искусственным интеллектом. Хотите научиться программированию, посвятить этому жизнь, стать востребован­ ным специалистом и получить престижную работу? Учите Python! Читайте эту книгу. Почему эта книга? В этой книге автор постарался: ♦ дать основы ♦ описать наиболее востребованные программные инструменты этого языка; ♦ показать, что с его помощью можно делать; ♦ и всё это- кратко, емко, доходчиво и без ненужной «воды». Эта книга - Python; для тех, кто намерен поскорее овладеть программированием на Python и не имеет возможности тратить на это слишком много времени. Для тех, кто хочет знать и уметь- и кому вечно некогда. Файловый архив Содержит все листинги, приведенные в книге, и результаты выполнения самостоя­ тельных упражнений. Доступен на сервере издательства «БХВ» по ссылке https:// zip.bhv.ru/9785977521260.zip, (см. приложение а также со страницы книги на сайте https://bhv.ru/ 4). Использованные программные продукты В табл. П.1 сведена информация о программных продуктах, которые использовал автор в процессе работы над книгой. Таблица П.1. ПО, использованное автором в процессе подготовки книги, и его версии Название Python Версия 3.13.4, 64-разрядная О Название Версия Pillow 11.3.0 requests 2.32.5 aiofiles 24.1 Beautiful Soup 4.13.4 aiohttp 3.12.15 NiceGUI 2.32.3 Tortoise ORM 0.25.1 Toga 0.5.2 Matplotlib 3.10.6
15 Предисловие Таблица П.1 (окончание) Версия Название Версия NumPy 2.3.3 LangChain 0.3.27 langchain-community 0.3.29 langchain-gigachat 0.3.12 ddgs 9.6.0 d uckd uckgo-sea rch 8.1.1 LangGraph 0.6.7 Название Типографские соглашения Для оформления различных языковых конструкций Python и программного кода в книге будут часто использоваться следующие типографские соглашения: ♦ в угловые скобки ( <>) заключаются наименования различных значений, которые дополнительно выделяются курсивом. В реальный код, разумеется, должны быть подставлены конкретные значения. Например: not <операнд> Здесь вместо подстроки операнд должно быть подставлено реальное значение операнда; ♦ в квадратные скобки ( [ J) заключаются необязательные фрагменты кода. Напри­ мер: exi t ( [ <код завершения>=О] ) Здесь необязательный параметр код завершения функции exi t () может указы­ ваться, а может и отсутствовать (тогда он получит значение по умолчанию О); ♦ вертикальной чертой ( 1) разделяются различные варианты языковой конструк­ ции, из которых следует указать лишь какой-то один. Пример: raise [<объект исключения> 1 <класс исключения>] Здесь следует поставить либо объект исключения, либо класс исключения; ♦ слишком длинные, не помещающиеся на одной строке языковые конструкции автор разрывал на несколько строк и в местах разрывов ставил знак ~- Напри­ мер: <имя файла с проrраммой> <интернет-адрес веб-страницы> ~ <путь к папке для сохранения изображений> Приведенный код разбит на две строки, но должен быть набран в одну. Сим­ вол~ при этом нужно удалить; ♦ тремя точками (. . . ) помечены фрагменты кода, пропущенные ради сокраще­ ния объема текста. Пример: from sys import exit
16 Предисловие if need_to_terrninate_prograrn_execution: exit () Здесь пропущен фрагмент кода между выражениями импорта и ветвления. Такое можно встретить в исправленных впоследствии фрагментах кода - при­ ведены лишь собственно исправленные выражения, а оставшиеся неизмененны­ ми пропущены. Также три точки используются, чтобы показать, в какое место должен быть вставлен вновь написанный код: в начало исходного фрагмента, в его конец или в середину - между уже присутствующими в нем выраже­ ниями; ♦ полужирным шрифтом выделен добавленный и исправленный код. Пример: fran argparse import Argumentparser print ( 'Создание миниатюр') Здесь вновь добавлено выражение импорта класса ArgurnentParser из модуля argparse; ♦ зачеркнутым шрифтом выделяется код, подлежащий удалению. Пример: from threadiR§ import Thread from queue import Queue frorn rnultiprocessing irnport freeze support, Process, JoinaЫeQueue Первые два выражения импорта следует удалить; ♦ рубленым шрифтом выделяются имена файлов, каталогов и пути к ним. Пример: modules\connection .ру. Внимание! Все приведенные здесь типографские соглашения имеют смысл лишь для оформле­ ния в книге языковых конструкций Python. В реальном программном коде использу­ ются только знак ~. три точки, полужирный и зачеркнутый шрифт.
ЧАСТЬI Язык Python: основные инструменты :> Урок 1. Введение в Python, часть 1 :> Урок 2. Введение в Python, часть 2 :> Урок 3. Логический тип данных, управляющие выражения и блоки :> Урок 4. Числа и строки :> Урок 5. Списки, кортежи, генераторы и словари :> Урок 6. Функции :> Урок 7. Классы и объекты :> Урок 8. Модули и пакеты :> Урок 9. Исключения и их обработка. Обработчики контекстов

Урок Введение в часть Python, 1 1 Принимаясь за дело, соберись с духом. Козьма Прутков К сожалению, компьютер не умеет непосредственно выполнять приложения, напи­ санные на Для этого ему потребуется помощь исполняющей среды (или Python. интерпретатора) особого программного пакета, который предварительно пре­ - образует Руthоn-приложения в представление, понятное центральному процессору компьютера. Загрузка и установка исполняющей среды жении Python описаны в прило­ 1. Начнем мы с самых азов. А расширим и углубим полученные здесь знания на сле­ дующих уроках. 1.1. Интерактивный режим Проще всего изучать Python, Python работая в интерактивном режиме вводя выраже­ - ния с клавиатуры и тут же получая результаты их выполнения. Выражение 11 программная инструкция, ~ записанная на языке программирования и выполняющая какое-либо законченное деиствие. Нам понадобится интерактивная оболочка - утилита, которая поставляется в со­ ставе исполняющей среды, передает набранные пользователем выражения испол­ няющей среде и отображает выданные ею результаты. Откроем меню Пуск, выведем список всех установленных программ, найдем в нем группу Python 3.13 (Python 3.13 64-Ьit) и выберем в ней пункт ШLЕ (Python 3.13 32-Ьit) или ШLЕ (в зависимости от установленной редакции исполняющей сре­ ды). На экране появится окно интерактивной оболочки IDLE ShelP (рис. 1.1 ). В верхней части окна будут выведены сведения об исполняющей среде Python, пустая строка в частности номер ее версии. Ниже этих сведений появится с мигающим текстовым курсором, дополнительно помеченная символами»>, рас­ положенными на вертикальной белой полосе слева (эти символы носят название приглашения). Именно в этой строке и вводится выражение, подлежащее выпол­ нению. 1 IDLE Shell (lntegrated Development and Learning Environment Shell) - для разработки и обучения. оболочка интегрированной среды
20 Часть l'° /. Язык Python: основные о *IDLE Shell 3.13.4* Eile .Edit She/1 инструменты .Qebug Qptions )tiindow х !:!elp Python 3 . 13 . 4 (tags/v3.13.4:8a526ec, Jun 3 2025, 17:46:04) [MSC v.1943 64 bit (АМD64)] on win32 Enter " help" below or click "Help" above for more information. • >>> • Ln: 3 Col: Рис. Выясним, владеет ли ла 24 и 38, Окно интерактивной оболочки 1.1. О IDLE Shell арифметическим сложением. Попробуем сложить чис­ Python набрав в упомянутой строке следующее выражение (рис. 1.2): 24 + 38 >» IEnter + 24 Рис. 1.2. >>> "help " 62 38j >>> Выражение , набранное в окне Enter "help" 24 + 38 Рис. IDLE Shell 1.3. Выражение и результат его вычисления Чтобы отправить введенное выражение исполняющей среде для выполнения , на­ жмем клавишу <Enter>. Вычисленный результат - сумма набранных чисел щей строке синим шрифтом (рис. 1.3). - будет выведен на следую­ Оrметим, что строка с результатом уже не помечается приглашением >>> . Под выведенным результатом появится новая строка для ввода с приглашени­ ем >» . Так что мы сразу можем набрать и выполнить другое выражение. Операторы. Приоритет операторов. 1.2. Скобки Рассмотрим только что набранное нами выражение и выданный (или, как говорят программисты, возвращенный) им результат: >>> 24 + 38 62 Внимание! В дальнейшем каждое выражение, набираемое в интерактивной оболочке, в тексте книги будет помечаться приглашением >». Само это приглашение вводить не сле­ дует . Результат вычисления выражения станет выводиться с отступом слева и без упомянутого приглашения. Более подробно типографские соглашения описаны в пре­ дисловии.
Урок 1. Введение в часть Python, 21 1 Как видим, арифметические операции записываются на Python в том же виде, что и в обычных школьных уравнениях. Это сделано для простоты и наглядности. Оператор языковая конструкция, выполняющая какое-либо элементарное дейст­ - вие над заданными операндами и, возможно, возвращающая результат. Операнд - значение, обрабатываемое оператором. Символ «плюс» это оператор сложения, который складывает два числовых ( +) - операнда и возвращает их сумму. Умножим 123 на 321, введя следующее выражение (символ звездочки* - это опе­ ратор умножения): >>> 123 * 321 39483 Разделим 19 на 3: »> 19 / 3 6.333333333333333 Результатом стало вещественное число. 11 Вещественное число, или число с плавающей точкой - дробное число. Целая и дробная части вещественного числа в Python разделяются точкой (а не за­ пятой). Зададим Python более сложную задачу, выполнив следующее выражение: >>> 3 + 12 * 4 51 Сначала выполнится умножение 12 на 4, а потом 3 будет сложено с полученным произведением. Приоритет оператора - задает очередность его выполнения. Операторы с большим приоритетом выполняются раньше, операторы с меньшим приоритетом - позже. Оператор умножения * имеет больший приоритет, нежели оператор сложения +, и, соответственно, выполняется раньше (как и в элементарной арифметике). На заметку Приоритеты операторов Python приведены в приложении 2. Операторы с одинаковым приоритетом выполняются в порядке слева направо: >>> 100 - 6 + 20 114 Символ «минус» (-) - это оператор вычитания. Его приоритет такой же, как и у оператора сложения. В этом случае сначала из числа ся разности прибавлено 20. 100 будет вычтено число 6, а потом к получившей­
22 Часть /. Язык Python: основные инструменты Повысить приоритет какого-либо оператора можно, заключив его вместе с операн­ 11 дами в круглые скобки. Выполним вот такое выражение: >>> (3 + 12) * 4 60 Сначала выполнится сложение чисел 3 и 12, поскольку соответствующий оператор заключен в скобки, а уже потом получившаяся сумма будет умножена на 4. Как ви­ дим, результат оказался совсем другим. Еще три примера: >>> 100 - 2 * 3 + 4 * 5 114 Порядок вычисления этого выражения таков: Умножение чисел 1. 2 и 3 (поскольку приоритет оператора умножения выше, чем у операторов вычитания и сложения). 2. Вычитание из 3. Умнржение 4. Прибавление к разности, полученной на шаге шаге 100 полученного на шаге 1 произведения. 4 и 5. 2, произведения, вычисленного на 3. >>> 100 - (2 * 3 + 4 * 5) 74 Здесь порядок вычисления будет другим: 1. Умножение чисел 2. Умножение 3. 2 и 3. 4 и 5. Сложение полученных произведений (поскольку оператор, выполняющий это сложение, находится в скобках). Вычитание из 4. 100 суммы, вычисленной на шаге 3. Скобки можно вкладывать друг в друга, еще значительнее повышая приоритет за­ 11 ключенных в них операторов: >>> 100 - (2 * (3 + 4) * 5) 30 Вот порядок вычисления: 1. Сложение чисел 3 и 4 (поскольку выполняющий это оператор находится во вло- женных скобках). 2 на полученную на шаге 2. Умножение 3. Умножение вычисленного на шаге 4. Вычитание из 100 1 сумму. 2 произведения на 5. произведения, полученного на шаге 3.
Урок 1. 1.3. Введение в Python, часть 23 1 Функции В результате деления 6. 333333333333333. 19 на 3 получилось слишком длинное вещественное число - Округлим его до второго знака после точки (не забываем, что целая и дробная части вещественного числа в Python разделяются точкой), для чего введем следующее выражение: >>> round(6.333333333333333, 2) 6.33 Функция языковая конструкция, выполняющая более сложное действие, чем опе­ - ратор. Может принимать произвольное количество параметров и возвращать резуль­ тат. Параметр - значение, обрабатываемое функцией. Дпя округления числа мы использовали функцию round () . Она принимает два па­ раметра: само округляемое число и количество цифр после точки, до которого это число следует округлить. Функция возвращает округленное число. Вызов функции 11 обращение к функции с передачей ей требуемых для работы пара­ метров. Чтобы вызвать функцию, после ее имени следует поставить круглые скобки, в ко­ торых через запятую указать передаваемые параметры. Мы вызвали функцию round (), передав ей параметры 6. 333333333333333 и 2. На заметку При указании функции в тексте, не являющемся выражением на языке Python (напри­ мер, в тексте руководства по языку), после имени функции всегда ставятся пустые круглые скобки - например, round (). Так читатель сразу поймет, что речь идет имен­ но о функции. Попробуем округлить до второго знака другое число: >>> round(6.777777777777777, 2) 6.78 Округлим то же число до четвертого знака: >>> round(6.777777777777777, 4) 6.7778 Необязательный параметр при вызове функции м~жно не указы~ать - ~тогда он по­ 11 лучит значение по умолчанию, установленное самои исполняющеи средои. Второй параметр функции round () является необязательным и имеет значение по умолчанию О (округление до целых). Проверим, так ли это, вызвав эту функцию без второго параметра: >>> round(6.333333333333333) 6 >>> round(6.777777777777777) 7
Часть 24 /. Язык Python: основные инструменты Функции max () и min () возвращают соответственно наибольшее и наименьшее из чисел, заданных в качестве параметров. Параметров у них может быть произволь­ ное количество. Проверим эти функции в деле: >>> max(l2, 3.4, 578, -1000) 578 >>> min(l2, 3.4, 578, -1000) -1000 1.4. Переменные. Присваивание Хватит отвлеченных упражнений! Займемся более практичными вещами. Переве­ дем несколько величин из дюймов в сантиметры. Вот только у нас нет желания каждый раз набирать длину одного дюйма в санти­ метрах, равную например 2,54. Поэтому сохраним это значение в переменной с именем, cms _ in_ inch. Переменная ячейка памяти, предназначенная для временного хранения какого­ - либо произвольного значения. Должна иметь уникальное имя, по которому впослед­ ствии будет выполняться обращение к этой переменной и которое можно выбрать произвольно. Создадим упомянутую переменную, введя следующее выражение: >>> cms in inch = 2.54 Присваивание - занесение заданного значения в указанную переменную. Оператор присваивания = (символ равенства) - присваивает значение, заданное справа, переменной, указанной слева. Если заданная переменная еще не существует, она будет создана. В нашем случае этот оператор присваивает число cms _ in_ inch, 2,54 переменной с именем попутно создав ее. Отметим, что оператор присваивания не возвращает никакого результата, так что под введенным нами выражением ничего не появится. Проверим, была ли создана эта переменная, обратившись к ней: >>> cms in inch 2.54 Обращение к переменной - извлечение хранящегося в ней значения с целью вывести его на экран или использовать в вычислениях. Выполняется простым указанием имени этой переменной. Интерактивная оболочка сразу же выведет значение этой переменной (у нас чис­ ло 2. 54).
Урок 1. Введение в Python, часть 25 1 Преобразуем в сантиметры величину 27 дюймов: >>> 27 * cms in inch 68.58 Здесь мы снова обратились к переменной cms _ in_ inch, хранящей количество санти­ метров в дюйме, указав имя этой переменной. 1О дюймах: Выясним, сколько сантиметров в >>> 10 * cms in inch 25.4 А сколько - в 120: >>> 120 * cms in inch 304.8 Имя переменной должно содержать лишь буквы 1 , цифры, символы подчеркивания и не должно начинаться с цифры. Допустимые имена переменных: а, ь, xl, х- 2, platfoпns, day- of- week, - other. Недопустимые имена переменных: day of week, Зх, is_it_correct?. Создадим еще одну переменную >>> >>> х - с именем х, присвоив ей число 200: = 200 х 200 Существующей переменной можно присвоить любое другое значение: >>> >>> х = 1 х 1 Значение, хранящееся в переменной, можно присвоить другой переменной - в нее будет занесена копия этого значения: >>>у= >>> х у 1 Внимание! Переменная может хранить лишь одно значение. После присваивания ей другого зна­ чения ранее хранившееся в ней значение будет безвозвратно потеряно. Прибавим к значению переменной х число >>> >>> х = х + 3 и вновь присвоим той же переменной: 3 х 4 1 Допускаются набирать. буквы любых алфавитов, но традиционно применяются лишь буквы латиницы - их проще
Часть 26 /. Язык Python: основные инструменты Созданные переменные будут существовать до тех пор, пока запущена интерактивная оболочка. Множественное присваивание - присваивание значений сразу нескольким перемен­ ным. Если требуется присвоить разным переменным одно и то же значение, достаточно записать выражение следующего вида: >>>а= Ь = 10 В результате одно и то же число >>> а >>> ь 1О будет присвоено двум переменным - а и ь: 10 10 Если же необходимо присвоить разным переменным разные значения, нужно при­ вести имена переменных через запятую слева от оператора присваивания, сваиваемые значения >>> d, с, е - а при­ также через запятую справа от оператора, например: = 20, 100, 4.82 Множественный вывод - вывод значений сразу нескольких переменных (или результатов вычисления нескольких выражений) в одной строке путем записи этих переменных (выражений) через запятую: >>> d, е (20, 100, 4. 82) с, Значения (результаты) будут выведены также через запятую и в круглых скобках (почему 1.5. 11 - мы узнаем в разд. 5.3.1). Типы данных Тип данных определяет характер значения и набор выполняемых с ним операций. Кратко рассмотрим наиболее часто употребляемые типы данных (более подробный разговор о них пойдет на следующих уроках). 1.5.1. Числа - целые и вещественные Два типа данных нам уже знакомы: ♦ целое число - записывается по привычным школьным правилам: 24, 123, 20000, -1000 ♦ вещественное - дробное число. Записывается также по школьным правилам, только целая и дробная части разделяются точкой, а не запятой. Примеры: 6.33, 2.54, 200.8, 0.123, -3.809
Урок Введение в 1. 1.5.2. часть 27 1 Строки Строка 11 Python, последовательность ~произвольны1f символов неограниченной длины, за­ ключенная в одинарные или двоиные кавычки . Наберем пару выражений, присваивающих разные строки разным переменным: >>> str 1 'Python' >>> str_2 = "Язык программирования" Мы можем объединять строки, воспользовавшись знакомым оператором+: >>> str 2 + str 1 'Язык программированияРуthоn' Забыли вставить пробел между словами ... Исправим ошибку: >>> str 2 + ' ' + str 1 'Язык программирования Python' Мы использовали строку с единственным символом - пробелом. Извлечем восьмой по счету символ из второй строки: »> str_2[7] 'о' Индекс - порядковый номер отдельного элемента в последовательности (например, символа в строке). Нумерация элементов последовательности начинается с О. Нужный нам восьмой символ имеет индекс 7. Мы указали этот индекс в квадрат­ ных скобках. И вполне ожидаемо получили букву «о» из слова «программиро­ вания». Извлечем первый с конца символ первой строки: >>> str_l[-1] 'n' Положительные индексы отсчитываются с начала последовательности, а отрицатель­ ные - с ее конца. Получим третий с конца символ той же строки: >» str_l [-3] 'h' Срез - фрагмент последовательности (например, строки). Получим срез второй строки, расположенный между шестым и тринадцатым сим­ волами включительно: »> str_2[5:13] 'программ' 1 Автор предпочитает одинарные кавычки (апострофы)- их проще вводить.
Часть 28 /. Язык Python: основные инструменты Мы указали в квадратных скобках индекс первого символа извлекаемого среза, двоеточие и индекс символа, следующего за последним символом требуемого среза (или, другими словами, последний символ в срез не попадает). Выясним длину второй строки в символах, применив функцию len () : >>> len(str 2) 21 В качестве строк можно представить отдельные слова, предложения и даже целые статьи, подлежащие какой-либо обработке. Очень и очень многие приложения, написанные на Python, работают со строками: переформатируют их, ищут в них требуемые фрагменты или преобразуют в веб-страницы. 1.5.3. Списки Список Python - пронумерованная последовательность элементов. Обращение к эле­ ментам списка выполняется по целочисленным индексам. Элемент - произвольное значение, хранящееся в составе списка (или иной последо­ вательности подобного рода). Для создания списка достаточно записать все входящие в него элементы через запятую и заключить их в квадратные скобки. Создадим два списка: первый - ставленными в виде строк, второй с названиями языков программирования, пред­ - со степенями числа >>> languages = ['Python', 'JavaScript', >>> powers_of_2 = [2, 4, 8, 16, 32, 64] Списки схожи со строками (см.разд. 'RuЬy', 1.5.2). 2: 'РНР'] Так, мы можем извлечь любой элемент, содержащийся в списке, по его индексу (не забываем, что нумерация элементов в списке начинается с О): >>> languages[l], powers_of_2[2], powers of 2[-2] ('JavaScript', 8, 32) Мы можем извлечь срез списка: >>> languages[0:3], powers of 2(3:5] (['Python', 'JavaScript', 'Ruby'], [16, 32]) Размер последовательности (например, списка) - количество хранящихся в ней элементов. Мы можем узнать размер списка, использовав функцию len (): >>> len(powers_of_2) 6 Еще мы можем добавить в список новый элемент или даже несколько элементов, оформив их в виде другого списка и прибавив его к первому списку оператором +: >>> powers_of_2 = powers_of_2 + [128, 256] >>> powers_of_2 (2, 4, 8, 16, 32, 64, 128, 256]
Урок 1. Введение в Python, часть 1 >>> languages = languages + ['Jvaa'] >>> languages ['Python', 'JavaScript', 'Ruby', 29 'РНР', 'Jvaa'] Также можно изменить значение любого элемента списка, записав этот элемент слева от оператора присваивания, а его новое значение - справа. Исправим назва­ ние языка программирования, хранящееся в последнем элементе первого списка: >>> languages[4] = 'Java' >>> languages [ 'Python', 'JavaScript' , 'Ruby', 'РНР', 'Java' ] Наконец, можно удалить любой элемент списка, воспользовавшись оператором del. Для примера удалим четвертый элемент первого списка: >>> del languages[З] >>> languages ['Python', 'JavaScript', 'Ruby', 'Java'] Список позволяет хранить в одной переменной произвольное количество разных значений и обращаться к ним по целочисленным индексам. Список также дает воз­ можность быстро перебирать элементы один за другим для выполнения над ними каких-либо действий. Кортежи 1.5.4. Кортеж: - неизменяемый список. Значения его элементов нельзя изменять, равно как нельзя добавлять в него новые элементы и удалять существующие (попытка сде­ лать это вызовет ошибку с выводом соответствующего сообщения). Кортеж создается так же, как и список, только набор элементов берется в круглые скобки: >>> = ('Windows', 'Linux', 'Android', 'rnacOS') platfoпns В остальном кортеж подобен списку: >>> platfoпns[0], ('Windows', >>> platfoпns[-2] 'Android') platfoпns[l:3], ( ( 'Linux', len(platfoпns) 'Android') , 4) Кортежи применяются в тех же случаях, что и списки, если набор содержащихся в них элементов не планируется изменять. Кортеж занимает меньше памяти, чем список с тем же набором элементов. Словари 1.5.5. Словарь - набор элементов, каждый из которых связан с определенным ключом. Обращение к элементам словаря выполняется по ключам. Ключ - уникальная пометка, связанная с каждым элементом словаря. Может быть выбран произвольно. Как правило, в качестве ключей используются строки удобства. - для
Часть 30 /. Язык Python: основные инструменты Чтобы создать словарь, следует записать пары вида «ключ элемента : значение эле­ мента» через запятую и заключить их в фигурные скобки: >>> pl = ('os': 'Windows', 'server': 'Node', 'client': 'React'I Обращение (см. разд. к элементу словаря 1.5.3), выполняется так же, как к элементу списка только в квадратных скобках указывается ключ требуемого эле­ мента: >>> pl [ 'server'] 'Node' Значения элементов словаря можно изменять: >>> pl['os'] = 'Linux' >>> pl ('os': 'Linux', 'server': 'Node', 'client': 'React'I Если присвоить значение элементу с несуществующим ключом, этот элемент будет создан: >>> pl['purpose'] = 'hosting' >>> pl ('os': 'Linux', 'server': 'Node', 'client': 'React', 'purpose': 'hosting'I Можно удалять элементы словаря, применив оператор del: >>> del pl [ 'purpose'] >» pl ('os': 'Linux', 'server': 'Node', 'client': 'React'I Функция len () вернет размер заданного словаря - количество хранящихся в нем элементов: >>> len(pl) 3 Словарь, в отличие от списка и кортежа, предоставляет доступ к элементам по на­ глядным ключам, а не по целочисленным индексам, которые легко забыть. Однако словарь занимает много памяти, отчего плохо подходит для хранения больших наборов значений, и не позволяет так же просто и быстро, как список или кортеж, перебирать элементы. Как правило, в виде словарей хранятся всевозможные дополнительные параметры и программные настройки, относительно немногочисленные. Стандартная библиотека. Модули. Импорт 1.6. Часть программных инструментов Python - в частности, операторы сложения, вы­ читания, умножения, деления и функция округления round () исполняющую среду. встроены в саму
Урок 1. Введение в Python, часть 1 Встроенная функция 11 31 функция, реализация которой встроена в саму исполняющую среду. Но абсолютное большинство инструментов этого языка реализовано в стандартной библиотеке. Стандартная библиотека - набор модулей, поставляемых вместе с исполняющей средой. Модуль Импорт файл с программой, реализующей различные программные инструменты 1 . - извлечение указанного программного инструмента из заданного модуля с целью использовать его в набираемых выражениях. Для вычисления квадратного корня служит функция sqrt (), реализованная в моду­ ле math стандартной библиотеки. Выполним импорт этой функции, записав вот такое выражение: >>> from math import sqrt Модуль или пакет, из которого следует выполнить импорт (у нас вается после ключевого слова Ключевое слово 11 рукций - math), записы­ from. слово, применяемое для написания различных языковых конст­ Python. Имя импортируемой из модуля сущности (например, нужной нам функции sqrt ()) указывается после ключевого слова import. Можно указать произвольное количест­ во имен, записав их через запятую. Извлечем квадратные корни из трех чисел, применив только что импортированную функцию: >>> sqrt(16), sqrt(2), sqrt(123) (4. О, 1. 4142135623730951, 11. 090536506409418) Вычислим синус, косинус и тангенс угла 30°, для чего применим функции sin (), cos () и tan (), реализованные в том же модуле math. Поскольку три упомянутые функции требуют указания угла в радианах, нам также понадобится функция radians () из того же модуля, преобразующая угол из градусов в радианы. Меньше слов - больше дела: >>> from math import sin, cos, tan, radians >>> angle_rad = radians(30) >>> angle_rad О.5235987755982988 >>> sin(angle_rad), cos(angle_rad), tan(angle_rad) (О. 4 9999999999999994, О. 8 660254 03784 4387, О. 577 3502 6918 96257) Пока мы занимались тригонометрией, нам поступил заказ от начальства: написать выражение, которое будет извлекать из кортежа с названиями языков программи- 1 Кстати, многие модули из стандартной библиотеки написаны на самом Python.
Часть 32 /. Язык Python: основные инструменты рования случайно выбранный язык. Как оказалось, отдел разработок не знает, на каком языке писать очередную программу ... Придется помочь будущим коллегам. Создадим сам кортеж с языками программирования: >>> languages = ('Python', 'JavaScript', Согласно документации по Python 'РНР', 'Ruby', 'Java') в модуле random имеется функция choice (), ко­ торая при каждом вызове возвращает случайный элемент заданной последователь­ ности. То, что нам нужно! Импортируем эту функцию: >>> from random import choice Выполним распоряжение начальства: >>> choice(languages) 'Java' choice(languages) >>> 'RuЬy' Вынос части функциональности языка в стандартную библиотеку позволяет «раз­ грузить» исполняющую среду, сделать ее компактнее и быстрее. 1.7. Объекты и классы Числа, строки, списки, кортежи и словари Python - значения довольно простые. Но может работать и с более сложными значениями, представленными в виде объектов. Введение в объекты и классы 1.7.1. Объект сложное значение, содержащее как набор входящих в него более простых - значений, так и инструменты для работы с ним. Создается на основе определенного класса. Атрибут - переменная, принадлежащая объекту и хранящая одно из входящих в не­ го простых значений. Метод - функция, принадлежащая объекту и предназначенная для выполнения каких-либо действий над ним. Класс - образец для создания объектов определенного вида. Задает набор атрибутов и методов, которые будут содержаться во всех объектах, создаваемых на основе этого класса. Возьмем для примера класс date, содержащийся в модуле datetime стандартной библиотеки. Он позволяет хранить и обрабатывать значения даты, представляющие собой комбинации года, номера месяца и числа. Импортируем этот класс: >>> from datetime import date
Урок 1. Введение в Python, часть 33 1 Создадим объект этого класса, хранящий даrу >>> current_date 16 июня 2025 года: = date(2025, 6, 16) Создание объекта на основе класса напоминает вызов функции (см.разд. 1.3). Мы указываем имя класса, ставим круглые скобки и в них записываем параметры - значения, которые будут занесены в создаваемый объект. Первым параметром задаем год, вторым порядковый номер месяца (июнь - месяцев начинается с 1), третьим - - 6-й месяц, нумерация число. Атрибут year класса date хранит год. Попробуем обратиться к нему: >>> current_date.year 2025 Чтобы обратиться к атрибуrу объекта, мы указываем сам объект, символ точки и имя требуемого атрибута. Текущий объект 11 объект, к атрибу~у которого в настоящий момент выполняется обращение или у которого в настоящии момент вызывается метод. Атрибуты month и day хранят номер месяца и число даты, хранящейся в текущем объекте: >>> current_date.month, current_date.day (6, 16) Метод isoweekday () класса date, не принимающий параметров, возвращает поряд­ ковый номер дня недели, к которому относится дата из текущего объекта, в форма­ те ISO (1 - понедельник, 2 - вторник и т. д.): >>> current_date.isoweekday() 1 16 июня 2025 года- это понедельник (1-й день недели). Метод replace () класса date возвращает копию даты из текущего объекта, у кото­ рой год, месяц и (или) число заменены на заданные в параметрах. Получим копию нашей даты, у которой год заменен на 2026-й, а месяц номер - на сентябрь (имеющий 9): >>> future_date = current_date.replace(2026, 9) >>> future date datetime.date(2026, 9, 16) Узнаем, на какой день недели приходится эта дата: >>> future_date.isoweekday() 3 Это среда (3-й день недели). 1.7.2. Объектная природа Python В Python значения любых типов представляют собой объекты соответствующих клас­ 11 сов.
34 Часть /. Язык Python: основные инструменты Так, целое число представляет собой объект класса int, вещественное число - объект класса float, строка- объект класса str, а список- объект класса list. Раз любое значение является объектом, мы можем обращаться к его атрибутам и вызывать у него методы. Класс строки str поддерживает не принимающий параметров метод upper (), воз­ вращающий копию текущей строки, у которой все буквы приведены к верхнему регистру: >>> s = 'Python' >>> s . uppe r ( ) 'PYTHON' А метод strip () класса str возвращает копию текущей строки, у которой удалены все начальные и конечные пробелы: >>> Python 'Python' '. strip () 1 Метод index () класса списка list возвращает индекс элемента текущего списка, хранящего заданное в параметре значение: >>> platfonns = ['Windows', 'Linux', 'Android', 'iOS'] >>> platfonns. index ( 'Linux') . 1 Строка 'Linux' хранится во втором элементе ( с индексом platfonns. 1.8. 1) списка из переменной Программные ошибки и сообщения о них Если при вводе выражения допустить ошибку, исполняющая среда откажется вы­ полнять выражение и выведет на экран сообщение об ошибке. Наберем и попытаемся выполнить выражение импорта с неправильно написанным ключевым словом import (подчеркнуто): >>> from datetime impor date SyntaxError: invalid syntax Появившееся ниже однострочное сообщение об ошибке состоит из двух частей: ♦ SyntaxError - тип ошибки (синтаксическая, т. е. некорректно написанное вы- ражение); ♦ invalid syntax - развернутое пояснение (неправильный синтаксис выражения). В других случаях сообщения об ошибках могут состоять из нескольких строк. На­ пример, попробуем сложить строку и число - что недопустимо: >>> 123 + 'аЬс' Traceback (most recent call last): File "<pyshell#25>", line l, in <module> 123 + 'аЬс' TypeError: unsupported operand type(s) for +: 'int' and 'str'
Урок 1. Введение в Python, часть 1 35 Рассмотрим это сообщение построчно: ♦ Traceback (most recent call last): С этой строки начинается так называемый стек вызовов, для нас сейчас беспо­ лезный. Мы рассмотрим его на уроке ♦ 6, когда будем заниматься функциями. File "<pyshell#25>", line 1, in <module> Местоположение выражения, содержащего ошибку. Фрагмент "<pyshell#<чиcлo»" указывает на то, что ошибочное выражение было введено в интерактивной обо­ лочке. ♦ 123 + 'аЬс' Само выражение, содержащее ошибку. ♦ TypeError: unsupported operand type(s) for +: 'int' and 'str' Описание ошибки из двух уже знакомых нам частей. Первая часть содержит тип ошибки - TypeError (неподходящий тип данных). Вторая часть - пояснение (оператор+ не позволяет складывать числа и строки). В табл. 1.1 приведены типы ошибок, которые могут встретиться нам на первых порах. Таблица 1.1. Типы наиболее часто встречающихся ошибок Тип ошибки Описание SyntaxError Синтаксическая ошибка (неправильно написанное ключевое слово, отсутствие закрывающей кавычки или скобки и т. п.) Неподходящий тип данных (например, использована строка вместо TypeError числа) Обращение к несуществующей переменной или вызов несуществующей NameError функции IndexError Обращение по индексу к несуществующему символу строки, элементу списка или кортежа KeyError Обращение по ключу к несуществующему элементу словаря ImportError Импорт из несуществующего модуля либо импорт из модуля несуществующего имени (переменной, функции или класса) AttributeError Обращение к несуществующему атрибуту или вызов несуществующего метода объекта В дальнейшем мы изучим еще несколько типов ошибок. 1.9. Что еще следует знать о часть Python в целом, 1 ♦ Все функции, с которыми мы имели дело на этом уроке, принимают лишь пози­ ционные параметры.
Часть 36 Позиционный параметр - /. Язык Python: основные инструменты параметр, идентифицируемый по его позиции. Так, функция round () принимает два позиционных параметра: >>> round(б.333333333333333, 2) Параметр, записанный в первой позиции, она идентифицирует как округляемое число, а параметр из второй позиции (если он задан) - как количество цифр после точки, до которых это число следует округлить. ♦ Есть возможность удалить созданную ранее переменную, воспользовавшись оператором del (см.разд. >>> str 3 = 1.5.3). Пример удаления переменной str_З: 'Язык программирования Python' >>> del str 3 ♦ В Python существует так называемый пустой тип данных NoneType. Единствен­ ное значение этого типа обозначается ключевым словом None и представляет отсутствие в переменной любого «значащего» значения. Пример: >>> some var = None Интерактивная оболочка не выводит значение None: >>> some var >>> Чтобы всё же вывести это значение, следует передать его в качестве параметра функции print () (с которой мы познакомимся в разд. 2.2.2), например: >>> print(some_var) None На практике значение None используется лишь в специфических случаях. ♦ В интерактивной оболочке выражениям и IDLE Shell результатам их можно перемещать курсор мыши по всем выполнения, выведенным в окне, выделять их с клавиатуры или мышью и копировать в буфер обмена. Находясь в строке с приглашением »>, также можно вырезать выделенный текст и вставлять туда содержимое буфера обмена. Для вырезания, копирования и вставки можно применять как стандартные ком­ бинации клавиш <Ctrl>+<C>, <Ctrl>+<X> и <Ctrl>+<V> соответственно, так и пункты Сору (Скопировать), Cut (Вырезать) и Paste (Вставить) меню Edit (Правка) окна IDLE Shell. ♦ Интерактивная оболочка IDLE Shel\ сохраняет все введенные пользователем вы­ ражения в хронологическом порядке в особом перечне, называемом историей. Любое из хранящихся в истории выражений можно извлечь, вывести в строке с приглашением >», изменить (например, исправить допущенную ошибку) и выполнить повторно. Для перемещения «назад» по истории следует последовательно нажимать ком­ бинацию клавиш <Alt>+<P>. Первое нажатие выводит ранее введенное выраже-
Урок 1. Введение в ние, второе Python, часть 37 1 выражение, набранное перед ним, и т. д. Переместиться «вперед» - по истории можно нажатиями комбинации клавиш <Alt>+<N>. Previous History (Назад по истории) Shell (Оболочка) окна IDLE Shell. Можно также использовать пункты History (Вперед по истории) меню и Next Внимание/ Все упомянутые комбинации клавиш работают при активной английской раскладке клавиатуры . В русской раскладке остается лишь пользоваться меню. ♦ В составе исполняющей среды также поставляется альтернативная консольная интерактивная оболочка (рис. 1.4). - запускающаяся в обычной командной консоли Она предлагает существенно меньшие функциональные возможности (в частности, не хранит историю набранных выражений), однако работает быст­ рее и отнимает меньше системных ресурсов . ~ Python 3.13 (64-Ьit) х + .., о х Python 3 . 13 . Ц (tags/v3 . 13 . Ц : 8a526ec, Jun 3 2025, 17 : Цб : 0Ц) [MSC v . 19ЦЗ bit (АМDбЦ)] оп win32 Туре "help", "copyright", "credits" or "license" for more information . >>> 2ц + 38 62 бЦ »> 1 Рис. 1.4. Окно консольной интерактивной оболочки Для запуска консольной оболочки следует открыть список всех установленных найти группу Python 3.13 и выбрать в ней пункт Python 3.13 (64-blt) (в зависимости от установленной программ в меню Пуск, Python 3.13 (32-Ьit) или редакции исполняющей среды).
Урок2 Введение в 2.1. Python, часть 2 Работа с данными разных типов В разд. 1.5 мы узнали, что значения, которыми оперирует Python, могут относиться к разным типам данных. При этом в ряде выражений могут быть использованы значения лишь строго определенного типа (например, в арифметических выраже­ ниях - 2.1.1. только целые и вещественные числа). Выяснение типа данных,. к которому относится значение Проверить, в какому типу относится заданное значение, можно с помощью встро­ енной функции type (). Вот формат вызова этой функции 1 : tуре(<Значение, тип которого необходимо выяснить>) Функция возвращает ссьmку на класс, к которому относится заданное значение. Для примера узнаем, к какому типу относится число 1492: »>а= 1492 >>> type(a) <class 'int'> Функция вернула ссылку на класс int, представляющий целые числа. Значит, 1492 - это целое число. Узнаем типы значений З >>> .14, 'Приложение' и { 'book': 'Python: 22 tуре('Приложение'), type({'book': 'Python: 22 (<class 'float'>, <class 'str'>, <class 'dict'>) tуре(З.14), Это объекты классов float, str и dict - урока'}) соответственно вещественное число, строка и словарь. А к какому типу относится значение даты (см.разд. 1. 7.1)? >>> frorn datetirne irnport date >>> type(date(2025, 6, 16)) <class 'datetirne.date'> 1 урока'}: Типографские соглашения, используемые в книге, описаны в предисловии.
Урок 2. Введение в Python, часть 39 2 Это объект класса date из модуля datetime. Обратим внимание, как записывается ссылка на класс, реализованный не в исполняющей среде, а в модуле стандартной библиотеки. 2.1.2. Преобразование значения в другой тип данных В ряде случаев можно преобразовать значение в другой тип данных. Для этого дос­ таточно создать объект класса, соответствующего требуемому типу данных, ука­ зав в качестве параметра преобразуемое значение в разд. ( создание объектов описано 1. 7. 1). Преобразуем число 1492 в строковый тип, создав на основе этого числа строку: >>>а= 1492 >>> str(a) '1492' Попытаемся аналогичным образом превратить строку '3 .14' в вещественное число: >>> float ( '3 .14') 3.14 Мы можем использовать полученное число в дальнейших вычислениях: >>> float('3.14') * 2 6.28 Превратим кортеж в список: »> list ( (1, 2, 3)) [1, 2, 3] Однако следует помнить, что не всякое значение может быть успешно преобразо­ вано в другой тип. Так, при попытке преобразовать в число строку, содержащую буквы, возникнет ошибка: >>> int ( 'abcde') ValueError: invalid literal for int() with base 10: 'abcde' То же самое произойдет при попытке преобразования числа в словарь: »> dict(234) TypeError: 'int' object is not iteraЫe Более подробно преобразование типов данных и проблемы, которые могут при этом возникнуть, мы рассмотрим на последующих уроках. 2.2. Написание программ на Python До этого момента мы работали в интерактивном режиме - вручную вводили вы­ ражения и тут же получали результаты их выполнения. Это хорошо при изучении языка и всевозможных экспериментах, но плохо при выполнении практических за-
40 Часть /. Язык Python: основные инструменты дач. В самом деле, чтобы повторно выполнить какие-либо сложные вычисления, нам придется вводить все требуемые выражения заново. Программа, w,u прw,ожение это набор выражений, сохраненный в файле для - дальнейшего использования. Выражения в файле с программой записываются в том же виде, что и в интерактив­ ной оболочке. Каждое введенное выражение должно завершаться переводом строки (нажатием клавиши <Enter>). Файлы с программами, написанными на Python, должны иметь расширение ру. В про­ тивном случае их нельзя будет запустить на выполнение щелчком мыши. Написав программу, мы можем в дальнейшем запускать ее на выполнение сколько угодно раз, задавая разные исходные данные. 2.2.1. 11 Ввод данных Ввод - получение программой исходных данных от пользователя. Ввод выполняет встроенная функция input (), вызываемая в следующем формате: input ( [ <Подсказка в виде строки>] ) Будучи вызванной, эта функция приостанавливает выполнение программы, ото­ бражает текстовый курсор и ждет, когда пользователь введет с клавиатуры какое­ либо значение и нажмет клавишу <Enter>. Если в необязательном параметре была задана подсказка, она будет выведена левее текстового курсора. Функция возвращает введенное пользователем значение в виде строки. Пример с присваиванием введенного значения переменной: input_value = inрut('Введите исходное число: ') В конце строки подсказки следует поместить пробел - чтобы вводимое значение не отображалось вплотную к подсказке. 2.2.2. 11 Вывод данных Вывод - отображение результатов вычислений, выполненных программой, на экране. Интерактивная оболочка сама выводит на экран результат вычисления каждого введенного выражения. Но в программе вывод нам придется выполнять самостоя­ тельно. Встроенная функция print () выводит на экран заданные значения. Формат ее вызо­ ва таков: рrint(<Значение 1>, <Значение 2>, ... , <Значение N>[, sep=' '] [, end=' \n']) Функция может принять произвольное число позиционных (идентифицируемых по их позициям) параметров экран. - выводимых значений. Все они будут выведены на
Урок 11 в 2. Введение Python, часть 41 2 параметр, идентифицируемый по его имени . Именованный параметр - Именованный параметр sep задает строку, вставляемую между отдельными значе­ ниями при их выводе (значение по умолчанию : строка с пробелом) . А именованный параметр end задает строку, которая будет отображена после вывода всех значений (по умолчанию : строка со специальным символом \ n, обозначающим перевод строки). Функция print () не возвращает результата. Мы можем испытать эту функцию в интерактивной оболочке: >>> print (1 , 2 , 3 , 4) 1 2 3 4 Как говорилось ранее, по умолчанию выводимые значения разделяются пробелами, а после их вывода выполняется перевод строки. Разделим выводимые значения запятыми: >>> print (1, 2, 3 , 4, sep= ', 1, 2 , 3 , 4 Завершим вывод строкой ' ') это в се ': >>> print (1, 2 , 3 , 4 , sep= ', Это в се 1, 2 , 3 , 4 ', end= ' Эт о в се ' ) Функцию print <) можно вызвать и вообще без параметров - в этом случае она выведет пустую строку: >>> print () >>> Написание программ в среде 2.2.3. Программный код, или просто код 11 IDLE Shell текст программы, написанный на каком-либо языке программирования . Писать Руthоn-программы можно в среде интерактивной оболочки дя программный код в отдельных окнах, называемых файловыми. IDLE Shell, вво­ Открыть новое , изначально пустое файловое окно можно, нажав комбинацию кла­ виш <Ctrl>+<N> или выбрав пункт New File (Новый файл) меню окно с уже набранным программным кодом показано на рис. ,,,. 2 . 1 .ру - D:/Dаtа/Документы/Работа/Книrи/Руthоn 22 урока Eile .Edit FQrmat Bun Qptions Window tlelp input_value = input ( ' Введите преобразуемую incheз = fl o at (input_value ) стз = incheз * 2 .54 print (' Результат : ' , сmз , ' см .' ) для на ... File (Файл). Такое 2.1. о величину в дюймах : х ') • 1 Ln: 5 Col: О Рис. 2.1. Файловое окно IDLE Shell с набранным кодом программы
42 Часть /. Язык Python: основные инструменты Откроем новое файловое окно и введем в него код программы, преобразующей заданную пользователем величину из дюймов в сантиметры (листинг 2.1 ). Листинr<J, 1.J)ро~мма, преобра~.,!'Мii~J1ны из input_value = inрut('Введите преобразуемую inches = float(input_value) cms = inches * 2.54 print ('Результат: ', cms, 'см. ' ) величину в дюймах: ') Первое выражение программы запрашивает у пользователя преобразуемую вели­ чину, применяя функцию input () (см.разд. 2.2.1). Второе выражение преобразует введенную величину в вещественный тип (чтобы можно бьmо выполнить необхо­ димые вычисления), третье - собственно переводит ее в сантиметры. И наконец, четвертое, вызывая функцию print () (см. разд. 2.2.2), выводит результат на экран. Сохраним набранный код в какой-либо папке в файле 2.1.ру. Для этого нажмем комбинацию клавиш <Ctrl>+<S> или выберем пункт Save (Сохранить) меню File файлового окна. При первом сохранении на экране появится стандартное диалого­ вое окно сохранения файла, в котором потребуется указать местоположение и имя создаваемого файла. При последующих сохранениях того же файла эти сведения уже указывать не придется. Запустим программу на выполнение, нажав клавишу Module (Запустить модуль 1 ) меню Run <F5> или выбрав пункт Run (Запуск). На передний план будет выведено главное окно IDLE Shell. В нем появится сооб­ щение о запуске написанной нами программы на выполнение и предложение вве­ сти преобразуемую величину в дюймах (рис. 2.2). >>> RESTART: .l.py = D:/Dаtа/Документы/Работа/Книги/Ру Введите преобразуемую величину в дюймах: Рис. Введем Предложение ввести преобразуемую величину в окне 2.2. величину, подлежащую преобразованию, нажмем 121 IDLE Shell клавишу и в следующей строке увидим выведенный программой результат (рис. <Enter> 2.3). >>> = RESTART: D:/Dаtа/Документы/Работа/Книги/Ру .1.ру Введите преобразуемую величину Результат: >» в дюймах: 12 см. 1 Рис. 1 30.48 2.3. Результат, выведенный программой Да, любой файл с Руthоn-программой является модулем. И он ничем не отличается от тех модулей, что входят в стандартную библиотеку и хранят части реализации Python.
Урок 2. Введение в часть Python, 43 2 Под результатом появится пустая строка с хорошо знакомым нам приглашени­ ем>». Это значит, что выполнение программы завершилось . Найдем созданный нами Руthоn-файл 2.1 . ру в Проводнике и щелкнем на нем мышью. Хранящаяся в этом файле программа запустится в консоли (рис. х @ C:\WINDOWS\ py.••• + о V 2.4). х Введите преобразуемую величину в дюймах : 3~ Рис. 2.4. Окно консоли с запущенной в нем Руthоn-программой (ожидает ввод) К сожалению, как только мы введем исходную величину и нажмем <Enter>, кон­ соль исчезнет, и мы не увидим выведенный результат. Дело в том, что наша про­ грамма завершает работу сразу после вывода результата, а ее завершение вызывает автоматическое закрытие окна консоли , в которой она запущена. Решить проблему можно, приостановив выполнение программы сразу после выво­ да результата. Мы сделаем это, добавив в конец программного кода еще одно вы­ ражение, вызывающее функцию input (). Тогда пользователь сможет просмотреть выведенный результат и закроет окно нажатием клавиши <Enter>. Откроем новое файловое окно, введем в него исправленный код программы из лис­ тинга 2.2 и сохраним в файле 2 . 2.ру. ~ Листинг 2.2. Исправленный код программы из листинга 2.1 input_value = inрut ( 'Вв едите преобраз уемую величин у = fl oat(input_value ) * 2 .54 print ( 'Результат:', cms, ' см .' ) input ( 'Дnя з авершения про г р аммы нажмит е <Enter>' ) в дюймах : ') cms совершенствование программы с целью ускорить ее выполнение и Оптимизация 11 уменьшить объем потребляемой ею памяти . Заодно мы немного оптимизировали код. Теперь второе выражение выполняет сра­ зу два действия: преобразует введенную величину в вещественный тип и переводит в сантиметры . Новая трех - см. листинг программа обходится лишь двумя 2.1 ), переменными (вместо благодаря чему экономится немного оперативной памяти и слегка повышается производительность . Щелкнем мышью на файле 2 . 2.ру, введем в появившемся окне консоли исходную величину, нажмем Нажмем еще раз <Enter> - <Enter>, и увидим вычисленный результат (рис. 2.5). чтобы завершить программу . Консоль закроется. IDLE Shell можно , нажав Open (Открыть) меню File. Открыть существующий файл с Руthоn-программой в комбинацию клавиш <Ctrl>+<O> или выбрав пункт В появившемся стандартном диалоговом окне открытия файла необходимо выбрать
Часть 44 ~ C:\WINDOWS\py.exe х + /. Язык Python: о V Введите преобразуемую величину в дюймах : Результат: 81.28 2.5. х 32 см. Для завершения программы нажмите Рис. основные инструменты <Enter~ Окно консоли с запущенной в нем Руthоп-программой (вывела результат) открываемый Руthоn-файл, который потом будет выведен в отдельном файловом окне. В среде IDLE Shell можно открыть произвольное количество файлов с Руthоn­ кодом. На заметку На начальном этапе изучения Python мы будем писать только консольные программы, выполняющиеся в консоли. Такие программы проще разрабатывать. На Python можно писать также графические и веб-приложения. Однако их разработка существенно сложнее и требует применения дополнительных библиотек. Программа­ ми такого рода мы займемся в части 111. 2.2.4. Альтернативные инструменты для написания программ Пользоваться для программирования оболочкой IDLE Shell не очень удобно. К счастью, существует ряд альтернативных инструментов, несравнимо более мощ­ ных и удобных. Автор рекомендует среду разработки Visual Studio Code 1 (рис. 2.6). Ее наиболее существенные преимущества: ♦ более удобные и развитые инструменты для написания кода; ♦ встроенный отладчик программ; ♦ возможность открыть папку и работать со всеми Руthоn-файлами, хранящимися в ней (что будет полезно при разработке сложных программ, код которых хра­ нится во множестве файлов); ♦ средства для работы с системами управления версиями; ♦ интеграция со службой искусственного интеллекта ♦ русская локализация. Microsoft Copilot; Также можно использовать аналогичные программные пакеты: PyChaпn2, SuЬlime Text3, Brackets4, Notepad++5 и т. 1 2 п. Ьttps://code.visualstudio.corn/. bttps://www.jetbrains.com/ru-ru/pycbarm/. 3 Ьttps://www.suЫimetext.com/. 4 Ьttps://Ьrackets.io/. 5 Ьttps://notepad-plus-plus.org/.
Урок Введение в 2. D Jlыдtлеt<ие [Jраека !Райл ,(1 Python, Вид часть Переход 2.2.ру Q [> х V 8 ') • 2.1 .ру input_value = input( 'Введите nреобразуему1О величину • дюймах: 22.ру смs = float(input_value) • 2 . 54 print( 4 2.1 .ру • х > Х • v 2 &9 • 2.2.ру • 22ру ОТКРЫТЫЕ Рt.ДАКТОРЫ о • 2 • V',sual Studio Codo • 2.1.ру Пl'ОВОДНИК V 45 2 'Ре:1ульт ■ т: input( •д,1я ', c11s, зааерwенмя 'см.') nporpa......, на:онпе <Enter> •) ♦ 22.ру ~ о о > СТРУКТУР,О ) Вl'ЕМЕННАIIШКАJIА Crpo,o, S. сrо,бец 1 Рис. 2.2.5. 2.6. Редактор Пробе,юе: 4 UТF-8 CRlf () Python в!\ 3.13.4 • Go t.м, 0 Visual Studio Code Комментарии Комментарий - произвольное пояснение, вставленное в код программы, предназна­ ченное исключительно разработчику и его коллегам и игнорируемое исполняющей средой. Комментарий начинается с символа решетки Python (#) и продолжается до конца строки. Примеры комментариев (подчеркнуты): # # Программа, в преобразующая введенную исходную величину из дюймов сантиметры cms = float(iпput value) * 2 .54 print ('Результат: ', cms, 'см. ') # Преобразуем в сантиметры Выводим результат Приостанавливаем выполнение программы для чтения результата input('Дnя завершения 2.2.6. Правила на Python ♦ # # программы нажмите <Enter>') набора программного кода Как упоминалось ранее, программа на языке стовом файле в кодировке Unicode Python сохраняется в обычном тек­ с расширением ру.
Часть 46 ♦ Как правило, каждое выражение Python /. Язык Python: основные инструменты занимает отдельную строку. Концом выражения является перевод строки. Пример такого кода можно увидеть в лис­ тингах 2.1 и 2.2. Код, в котором каждое выражение занимает отдельную строку, лучше читается. ♦ Короткие выражения можно набирать в одну строку, разделяя их символами точки с запятой(;). Концом последнего выражения станет перевод строки. При­ мер: а= 3; Ь = б; с= а/ Ь; print(c) Такой код компактнее, но читается хуже. ♦ Если выражение не помещается в одной строке, его можно разделить на не­ сколько отдельных строк (подстрок). Переводы строк, разделяющие выражение на подстроки, вставляются между от­ дельными языковыми конструкциями: • внутри круглых, квадратных или фигурных скобок print( - в любом месте: print ('Результат: ', 'Результат:' crns, 'см.') crns, 'см.' 1st [ 'Python', 'JavaScript', dct 'server': 'Python', 'client': 'Lit' 'RuЬy', 'РНР'] • вне скобок ного слеша - перед (\): переводом строки необходимо поставить символ обрат­ cms = float(input_value) * \ 2.54 Как правило, у второй и (см. приведенные примеры) year current date.\ year последующих подстрок делаются отступы - слева так сразу станет понятно, что эти подстроки яв­ ляются составными частями одного выражения. Внимание/ Отступы в Python должны формироваться исключительно пробелами. Символы табу­ ляции недопустимы. Наличие символа табуляции в составе отступа приведет к воз­ никновению ошибки типа тaьError. ♦ Между отдельными языковыми конструкциями можно вставлять произвольное количество пробелов. Так, следующие три выражения полностью идентичны: cms = float(input_value) * 2.54 cms float(input_value) * cms=float(input_value}*2.54 2.54
Урок 2. Введение в Python, часть 47 2 Лучше ограничиться одним пробелом. В противном случае код будет плохо чи­ таться. ♦ Строки кода можно разделять произвольным количеством пустых строк. 2.2.7. Сообщения об ошибках в коде программ Сообщение об ошибке, допущенной в коде программы, содержит точное указание на место, где присутствует ошибка. Это указание находится во второй строке сообщения и выводится в формате: File "<полный путь in <имя функции, в к модулю с опмбкой>", <номер строки с опмбкой>, line опмбка> которой возникла ~ < module > 1 Имя функции будет выведено лишь в том случае, когда ошибка возникла в теле ка­ кой-либо функции (подробности - в разд. 6. 7). Если ошибка возникла в обычном коде, будет выведено слово <module>. Так, если в строке № 3 программы из листинга 2.2 вместо функции print () набрать prnt () , будет выведено следующее сообщение (указание на местоположение ошиб­ ки подчеркнуто): Traceback (most recent call last): File "D:\work\2.2.py", line З, in <module> prnt ('Результат:', cms, 'см.') NameError: name 'prnt' is not defined. Did you mean: 'print'? 2.3. Что еще следует знать о часть в целом, 2 ♦ В функциях Python именованными обычно делаются необязательные параметры (как в функции print () - см. разд. глядности кода (подробности ♦ Python Программу из см. листинг 2.3. листинга - 2.2 2.2.2) на уроке 6). можно для удобства их указания и ради на­ оптимизировать еще значительнее - llfтинr2.3.011т111J-lii'i кодnроrр~~~-'11"~нга 2.2 input_value = inрut('Введите преобразуемую величину в дюймах: ') float(input_value) * 2.54, 'см.') завершения программы нажмите <Enter>') рrint('Результат:', input('Для Здесь код, преобразующий введенную величину в сантиметры, вставлен непо­ средственно в функцию print (), выполняющую вывод, в качестве второго пара­ метра. Исполняющая среда сначала выполнит этот код, а потом передаст выдан­ ный им результат функции print (). В результате отпадает необходимость еще в одной переменной, что сэкономит еще немного памяти.
Часть 48 ♦ /. Язык Python: основные инструменты Если в коде программы вычисленный результат не присваивается переменной, не передается вызываемой функции в параметре и не выводится на экран, он будет безвозвратно потерян. ♦ При возникновении синтаксической ошибки исполняющая среда пытается уга­ дать, что имел в виду разработчик программы, когда писал код. Так, если вместо функции print () написать prnt (), исполняющая среда выведет следующее со­ общение: NameError: name 'prnt' is not defined. Did you mean: 'print'? 1 ♦ Перед выполнением любого модуля исполняющая среда выполняет комnWIЯцию его кода - преобразование в компактное внутреннее представление, называе­ мое байт-кодом. Это делается для повышения производительности. Байт-код модуля, непосредственно запущенного на исполнение, сохраняется в оперативной памяти, а байт-код модулей, из которых был выполнен импорт каких-либо сущностей, - в файлах. Сохранение байт-кода импортированных модулей в файлах позволяет ускорить запуск других программ, также импорти­ рующих что-либо из этих модулей 2 . 1 «Имя prnt не существует. Вы имели в виду: print?» (англ.) Установщик исполняющей среды Python предлагает откомпилировать всю стандартную библиотеку непо­ средственно при установке (флажок Precompile standard library - см. приложение 1). Настоятельно реко­ мендуется так и сделать это существенно ускорит запуск программ. 2
УрокЗ Логический тип данных, управляющие выражения и блоки Итак, началами Python мы овладели - на уроках 1 и 2. Настала пора заняться уг­ лубленным изучением этого замечательного языка. Обычно выражения, из которых состоит программа, выполняются однократно, в порядке от начала программы к ее концу или, другими словами, сверху вниз. На уроке 2 мы в этом убедились. Но что делать, если, например, нужно выполнить какое-либо выражение только в том случае, если указанная переменная хранит значение О? Использовать управ­ ляющие выражения. 1Управляю'1}-ее выра:ж:ение 11 сложное выражение, управляющее выполнением других выражении. Управляющие выражения достаточно сложны, включают в себя более простые вы­ ражения (выполнением которых они и управляют) и располагаются на нескольких строчках. 3.1. Логический тип данных 3.1.1. Введение в логический тип. Операторы сравнения.Условия Python поддерживает еще один тип данных - логические величины. Логическая величина может принимать лишь одно из двух значений, обозначаемых ключевыми словами True (истина) и False (ложь). Оператор сравнения 11 операто~, сравнивающий значения ~перандов согласно за­ данному правилу и возвращающии результат в виде логическои величины. Оператор сравнения > (символ «больше») возвращает логическую величину тrue, если значение левого операнда больше значения правого, и False - в противном случае. Проверим, так ли это, набрав в интерактивной оболочке следующие выражения: >>> n = 2 >>> n > О True
Часть 50 /. Язык Python: основные инструменты >>> n = -6 >>> n > О False Оператор сравнения меньше правого, и < (символ «меньше») возвращает True, если левый операнд False - в противном случае. А оператор сравнения == (два знака равенства) проверяет, равны ли значения операндов: >>> n == О False >>> n == -6 True Остальные операторы сравнения мы изучим на следующих уроках. Кроме операторов, результаты в виде логической величины возвращают многие функции и методы. Так, метод isalpha () класса str возвращает True, если текущая строка содержит только буквы, и False - если в ней есть и символы, не являющие­ ся буквами: >>> 'abcde' .isalpha(), 'абвгд' .isalpha(), 'abc12345aбв'.isalpha() (True, True, False) Условие - выражение, в качестве результата возвращающее логическую величину. Выполняющееся, или истинное, условие - Не выполняющееся, или ложное, условие Логическая величина 3.1.2. возвращающее тrue. - это объект класса - возвращающее False. bool. Логические операторы Ранее мы рассмотрели примеры простых условий. Однако Python позволяет писать и более сложные условия, включающие произвольное количество операторов срав­ нения и логических операторов. Логический оператор 11 ~ператор, п~инимающий в качестве операндов логические величины и возвращающии логическии же результат. Поддерживаются три логических оператора: ♦ not <операнд> (логическое НЕ) - возвращает False, если значение операнда рав­ но True, и True если значение операнда равно False. Или, другими словами, меняет значение операнда на противоположное: >>> not True, not False (False, >>> result ♦ True) = not m > 10 # Значение переменной т НЕ больше <операнд 1> and <операнд 2> (логическое И) обоих операндов равны True, и False - - возвращает True, если значения в противном случае: >>> True and True, False and True, True and False, False and False (True, False, False, 10 False)
Урок 3. Логический тип данных, управпяющие выражения и блоки >>> m = 4 >>> result = m > 10 and n < 100 # т больше 10 И п меньше 51 100 or <операнд 2> (логическое ИЛИ) - возвращает True, если значе­ ние хотя бы одного из операндов равно True, и False - в противном случае: <операнд 1> ♦ >>> True or True, False or True, True or False, False or False False) True, True, (True, # т больше 10 ИЛИ п меньше 100 >>> result = m > 10 or n < 100 Выражение ветвления 3.2. Выражение ветвления., или условное выражение няет блок 1, при истинности условия 2 - ложны, будет выполнен блок else при истинности условия 1 выпол­ - блок 2 и т. д. Если все указанные условия (если он указан - в противном случае ничего не произойдет). Выражение ветвления может быть записано в одном из двух форматов: полном и сокращенном. Полный формат выражения ветвления таков: if <условие <блок e1if 1>: 1> <условие <блок elif <условие <блок 2>: 2> N>: N> [else: <блок else> elif, не ограничено. Симво­ else обязательны. Количество условий, задаваемых ключевыми словами лы двоеточия после условий и ключевого слова Пример выражения ветвления: if n > 100: рrint('Значение переменной n больше 100') n больше 50') n больше 10') n не elif n > 50: рrint('Значение переменной elif n > 10: рrint('Значение переменной else: рrint('Значение переменной больше 10') Для практики напишем простенькую программу-экзаменатор, выдающую вопрос и четыре пронумерованных варианта ответа. Пользователь введет номер выбранного им ответа, и программа сообщит, является ли он правильным.
Часть 52 Код программы приведен в листинге /. 3.3). Я print ( 'Варианты 1) цвет фона;') начертание шрифта;') print(' 3) кегль print (' 4) скорость print (размер) шрифта;') анимации.') inрut('Введите номер ответа: ') == '3': ('Да! nшnЬer elif описано font-size?') 2) nшnЬer - ответа:') print (' print (' if это важно (почему - ваш экзаменатор.') - print('Чтo задает СSS-атрибут = основные инструменты И сохраним в файле 3.1.ру. рrint('Здравствуйте! nшnЬer Python: Наберем его, обращая внимание на все 3 .1. отступы слева и формируя их только пробелами в разд. Язык Правильно!') == '2': print ( 'Нет. Но тепло.') else: print ('Нет. input ( 'Нажмите Холодно! клавишу ') <Enter> для завершения программы. ') Код программы достаточно прост и нагляден и не требует пояснений. Единствен­ ное: не забываем, что функция input () возвращает введенное значение в строковом виде, и поэтому его следует сравнивать со строками '3' и '2 ', а не с числами 3 и 2. Запустим программу и попробуем ввести какой-либо номер. Посмотрим, что полу­ чится. Выражение ветвления часто состоит лишь из одного условия 1 и блока else: if n > 100: рrint('Значение переменной n больше n не 100') else: рrint('Значение переменной больше 100') Или даже из единственного условия 1: if n > 100: рrint('Значение переменной n больше 100') Тогда, если значение переменной n не больше 100, ничего не произойдет. Выражения ветвления можно вкладывать друг в друга: if n > 100: if rn = 10: print ( 'n больше 100 и rn равно О')
Урок 3. Логический тип данных, управляющие выражения и блоки else: print('n больше 100 и m else: print ( 'n не больше 100') 53 не равно О') Сокращенный формат выражения ветвления позволяет указать лишь одно условие и записывается так: <значение if> if <условие> else else> <значение Если условие истинно, возвращается значение if, если ложно - значение else. Пример: result = 'больше рrint('Значение 3.3. Блок 100' if n > 100 else переменной n', result) 'не больше 100' Блоки. Пустой оператор - набор из произвольного количества выражений, входящий в состав более сложного выражения (например, ветвления). Выражения, входящие в блок, должны набираться с отступами слева, формируемыми только пробелами и имеющими одинаковый размер. Например, в следующем фрагменте кода: i f n > 100: рrint('Значение переменной n больше 100') выражение рrint('Значение переменной n больше 100') является блоком, входящим в состав выражения ветвления. Поэтому оно набрано с отступом слева. Как правило, отступ у блока делается равным сти кода используют отступы в 4 пробелам. Иногда ради компактно­ 2 пробела 1 . Внимание! Формирование блоков символами табуляции не допускается и приведет к ошибке типа TabError. Блок может содержать произвольное количество выражений. Так, в следующем примере блок содержит два выражения: if n > 100 and m == рrint('Значение print('A О: переменной значение n переменной больше m равно 100') О') Признаком конца блока является выражение без отсrупа слева. 1 Но такой код, на взгляд автора, хуже читается.
Часть 54 /. Язык Python: основные инструменты Блок может содержать сложное выражение, включающее, в свою очередь, другие блоки: if n > 100: if m == 10: print ( ' n else: print('n больше 100 и m равно боль ше 100 и m не равно О ' ) О ' ) Обратим внимание, что блоки, входящие в состав другого блока, должны иметь удвоенные отступы слева . Блок должен содержать хотя бы одно выражение . Если по какой-либо причине ни одного выражения в блок поместить нельзя, следует вставить в него пустой оператор pass, if который ничего не делает, например: аЬс == 1000: pass Как набираются выражения, содержащие блоки? 3.3.1. Интерактивная оболочка IDLE Shell по мере сил помогает программисту набирать выражения с блоками . После строки, ввода любой заканчивающейся с ключевым словом if и условием- см.разд. дующая строка будет иметь отступ слева в 4 3.2), (например, двоеточием и нажатия клавиши строки <Enter> сле­ пробела, автоматически сформирован­ ный оболочкой . Строка с отступом будет дополнительно помечена приглашением в виде трех точек ( ... ). После ввода первого выражения, входящего в набираемый блок, и нажатия клави­ ши <Enter> следующая строка также будет иметь отступ слева и приглашение Так что сразу же можно набирать следующее выражение блока. Чтобы завершить ввод блока, необходимо убрать отступ слева, нажав клавишу <Backspace>. Чтобы после набора блока выполнить выражение, следует нажать клавишу <Enter> дважды. На рис. 3 .1 показано выражение ветвления с двумя блоками, набранное в интерак­ тивной оболочке. >>> n = 100 >>> Н n > 100 : рrint(' Значение ~ переменной n больше переменной n не 100 ') f" : рrint( 'Значение больше 100 ') ··· 1 значение переменной n не больше 100 >» 1 Рис. 3.1. Выражение, содержащее блоки , набранное в интерактивной оболочке
3. Логический тип данных, управляющие выражения и блоки Урок Выражение выбора 3.4. Выражение выбора если проверяемое значение совпадает с образцом 1, выпол­ - няет блок 1, если совпадает с образцом 2 - ( если он указан - из одним с ни совпадает не 55 блок 2 и т. д. Если проверяемое указанных образцов, будет выполнен значение блок в противном случае ничего не произойдет). Формат выражения выбора: match <проверяемое значение>: case <образец 1>: <блок case 1> <образец <блок case <образец <блок 2>: 2> N>: N> [case <блок >] Количество образцов, задаваемых ключевыми словами case, не ограничено. Все отступы слева должны быть соблюдены. Двоеточия после проверяемого значения и образцов обязательны. Пример выражения выбора: match language: case 'Python' : print ('Лучший в case 'JavaScript': print('Ocнoвa case 'Java' : print ('Для case 'HTML' : мире язык программирования! ') клиентской веб-разработки') написания мобильньD{ приложений') print('Ocнoвa клиентской веб-разработки') case print ( 'Таких языков не После ключевого слова знаем') case можно указать несколько образцов, разделив их симво­ лами вертикальной черты ( 1 ). Тогда соответствующий блок будет выполнен, если проверяемое значение совпадет с любым из заданных образцов. Пример: match language: case 'JavaScript' print('Ocнoвa 1 'HTML': клиентской веб-разработки') Также после образца можно поставить дополнительное условие, записанное в сле­ дующем формате: case <образец> if <дополнительное условие>:
Часть 56 /. Язык Python: основные инструменты В этом случае соответствующий блок будет выполнен, если проверяемое значение совпадет с образцом и окажется истинным дополнительное условие. Пример: rnatch language: case 'Python' if language versi on == 2: рrint('Устаревшая версия Python. ') print ('Не пользуйте с ь ей! ') case ' Python' : print ( 'Лучший в мире язык пр о граммирования! ' ) Мы можем переписать программу-экзаменатор из листинга выражения выбора. Листинг Листинг 3.2. 3.2 3. 1 с использованием приводит фрагмент этой программы. Переделанная программа-экзаменатор из листинга 3.1, использующая выражение ветвления (исправления) match numЬer: case '3': print ('Да! Прави.пьно ! ') case '2': print ( 'Нет. Но 'l'eПJJO. ' ) 'Нет. Холодно! case print ( ') Выражение выбора можно рассматривать как более наглядную разновидность вы­ ражения ветвления, в котором каждое из условий сравнивает проверяемую вели­ чину с одним из образцов. 3.5. Циклы. Цикл с условием Цикл 11 фрагмент программного кода, многократно выполняющийся раз за разом. Цикл с условием - цикл, выполняющийся, пока остается истинным заданное условие 1. Цикл с условием записывается согласно следующему формату: while <условие> : < тело цикла > Тело цикла - фрагмент кода, который и будет выполняться многократно. Оформля­ ется в виде блока. Итерация цикла 1 - одно из выполнений тела цикла. Это одна из разновидностей циклов, поддерживаемых Python. Другую разновидность мы изучим на 4. уроке
Урок Логический тип данных, управляющие выражения и блоки 3. 57 Следующий код последовательно выведет на экран числа от 1 до 10 (можете на­ брать его в интерактивной оболочке и проверить): i = 1 while i < 11: print(i) i = i + 1 Мы создаем переменную i и присваиваем ей первое из чисел, подлежащих выво­ ду, - 1. В цикле записываем условие, которое остается истинным, пока значение 11. В теле цикла выводим число из переменной i на экран и увеличиваем его на 1. Когда будут выведены требуемые числа 1-10, переменная i получит значение 11, условие в цикле станет ложным, и выполнение цикла завер­ переменной i меньше шится. В теле цикла с условием необходимо выполнять какие-либо действия, которые в нуж­ ный момент сделают условие, записанное в цикле, ложным и тем самым завершат выполнение цикла. Если таких действий не выполнять, цикл станет бесконечным. Бесконечный цикл - цикл, выполняющийся вечно. Вызывает зависание и даже ава­ рийный останов программы. В листинге 3 .3 представлен код программы, выводящей синусы углов в диапазоне 0-360° с шагом в 10° с помощью цикла с условием. from math import sin, radians deg = О while deg <= 360: r = radians(deg) print('sin(', deg, deg = deg + 10 '):' sin(r), sep=") Оператор сравнения <= возвращает тrue, если значение левого операнда меньше или равно значению правого, и False - в противном случае. Перед вычислением синуса не забываем преобразовать угол в радианы вызовом функции radians () из модуля math. В теле цикла после вывода очередного значения синуса увеличиваем значение угла на 1О. Обратим внимание, как выполняется вывод. В вызове функции print () мы указы­ ваем: строку 'sin ( ', значение угла в rрадусах, строку '): ', значение синуса этого угла, и убираем пробелы, по умолчанию вставляемые между выводимыми значениями, дав параметру sep значение в виде пустой строки. В результате вывод программы будет таким: sin(O): sin(lO): sin(20): О.О О.17364817766693033 О.3420201433256687
Часть 58 /. Язык Python: основные инструменты Если же мы не укажем параметр sep с пустой строкой, программа выведет вот что: О sin ( ): О. О sin( 10 ) : sin ( 20 ) : О.17364817766693033 О. 3420201433256687 Некрасиво, согласитесь. 3.6. Управление циклами 3.6.1. Переход на следующую итерацию Оператор continue немедленно~ прерывает выполнение текущей итерации цикла и на­ 11 чинает выполнение следующеи итерации. Операндов он не принимает. Следующий код выведет на экран числа i = 1-10, за исключением чисел 3 и 4: о while i < 11: i = i + 1 if i == 3 or i 4: continue print (i) Обратим внимание, что здесь переменной i изначально присваивается значение О, а увеличение значения этой переменной на 1 производится в начале тела цикла. В результате первым выведенным на экран числом окажется 3.6.2. 1. Прерывание цикла Оператор ьreak, не принимающий операндов, немедленно прерывает выполнение 11 цикла. Следующий пример, как и приведенный в разд. однако реализован с применением оператора i = 3.5, выводит на экран числа 1-10, break: 1 while True: print(i) i = i + 1 if i > 10: break Здесь мы указали в цикле в качестве условия значение True, так что цикл, по идее, должен оказаться бесконечным. Однако в теле цикла после приращения значения переменной i на ной 1О, 1 выполняется проверка, превысило ли значение этой перемен­ и если превысило, цикл прерывается.
Урок Логический тип данных, управляющие выражения и блоки 3. 59 Автоматическое преобразование типов в условиях управляющих выражений 3.7. В разд. 2.1.2 описывалось преобразование значений в другой тип. И это преобразо­ вание мы выполняли явно. Однако в любом из рассмотренных ранее выражений в качестве условия можно указать значение типа, отличного от логического, :_ и исполняющая среда авто­ матически преобразует это значение в логический тип. Правила преобразования достаточно просты и приведены в табл. Таблица В • True 3 .1. 3.1. Правила автоматического преобразования типов преобразуются: В False преобразуются: любое ненулевое число: • число о • пустая строка ( ' ') • пустой список ( []) • пустой кортеж ( () ) • пустой словарь 10,-20,0.345 • любая непустая строка: 'Python', • 'Язык программирования' любой непустой список: [1, 2, 3], • 'Ь', 'с', 'd' ], [345] ICI f 'd'), (345,) любой непустой кортеж: (1, 2, 3), • ['а', ('а', 'Ь', любой непустой словарь: ( 'lang': ' Python' }, ( ' а' : в, 'Ь': ( ( }) 57.2) В качестве примеров рассмотрим приведенные далее пары выражений. В каждой паре левое и правое выражения полностью идентичны. Оператор сравнения ! = воз­ вращает True, если значения операндов не равны, и if п != О: if n == О: if len(languages) > о: if n == о and len(languages) > 3.8. False - в противном случае: if n: if not n: О: if languages: if not n and languages: Оператор присваивания в составе выражения Оператор присваивания в составе выражения 11 ния=) возвращает результат - Рассмотрим следующий пример: 1 = len(s) if 1 > О: print ( 'Дпина строки:', 1) = (в отличие от оператора присваива­ присвоенное значение.
Часть 60 /. Язык Python: основные инструменты Его можно немного сократить, применив оператор присваивания в составе выра­ жения: if (1 := len(s)) > О: рrint('Дпина строки:', 1) Условие, включающее оператор присваивания в составе выражения, следует взять в круглые скобки, поскольку этот оператор имеет крайне низкий приоритет - даже ниже, чем у операторов сравнения. 3.9. Что еще следует знать о блоках и не только ♦ Выражение ветвления с единственным условием и блоком, состоящим из одного короткого выражения, можно записать в одну строку: if n > 100: рrint('Значение переменной n больше 100') Цикл с условием, тело которого состоит из одного или нескольких коротких выражений, также можно записать в одну строку: i = 1 while i < 11: print(i); i = i + 1 Такая запись компактнее, но хуже читается. ♦ В Python блок всегда ставится после строки, заканчивающейся двоеточием. Так, в выражении ветвления блоки ставятся после строк с ключевым словом if или elif и условием, а также после строки с ключевым словом else (двоеточия подчеркнуты): if n > 100: рrint('Значение переменной n больше 100') n больше 50') n не elif n > 50: рrint('Значение переменной else: рrint('Значение переменной В других управляющих выражениях ♦ - больше 50') выбора и циклах - дело обстоит так же. Проверить, относится ли заданное значение к требуемому типу, можно, сравнив результат, возвращенный функцией type ( J (см.разд. # Да, Нет, переменная тип с требуемым типом: ли значение переменной п к целочисленному типу # Выясняем, относится if type(n) == int: else: # 2.1. 1), п хранит целое число значения переменной пне является целочисленным ♦ Проверку принадлежности указанного значения заданному типу данных также МОЖНО ВЫПОЛНИТЬ С помощью функции isinstance (): isinstance(<знaчeниe>, <тип данных>)
Урок 3. Логический тип данных, управляющие выражения и блоки 61 Функция возвращает True, если значение относится к заданному типу, и False в противном случае. Пример: if isinstance(n, int): # Да, переменная п хранит целое число else: # Нет, тип значения переменной пне является ♦ целочисленным Любое значение можно преобразовать в логический тип явно, просто создав на его основе объект класса bool. Соответствующее выражение записывается в формате: Ьооl(<преобразуемое значение>) Примеры: >>> bool(l23), bool(' ') (True, False) ♦ В модуле sys реализована функция exi t (), немедленно завершающая выполне­ ние программы с заданным целочисленным кодом завершения: exi t ( [ <код завершения>=О) ) Обычно эта функция применяется в управляющих выражениях, например: frorn sys irnport exit if need to terrninate_prograrn_execution: exit () 3.1 О. 1. Самостоятельные упражнения Напишите, используя выражение ветвления, программу-экзаменатор, ряющую, знает ли пользователь назначение HTML-тera 2. прове­ <select>. Напишите аналогичную программу, только уже с применением выражения вы­ бора. 3. Напишите программу, которая выводит косинусы углов только в обратном порядке (от 0-360° с шагом 10°, 360 до 0°). В этом случае вам пригодится оператор сравнения >=, который возвращает тrue, если значение левого операнда больше или равно значению правого, и False в противном случае.
Урок4 Числа и строки 4.1. Числа Числа - целые и вещественные - это наиболее фундаментальная разновидность данных, обрабатываемых компьютером. Все остальные типы словари - - строки, логические величины, даже списки и в конечном итоге сводятся к числам. Целые числа являются объектами класса int, вещественные - объектами класса float. 4.1.1. Запись чисел Числа в Python записываются ♦ целые • - в следующем виде: в зависимости от системы счисления: десятичные - по обычным школьным правилам: 24, 123, 20000 • шестнадцатеричные - с префиксом из цифры О и латинской буквы х в лю­ бом регистре. Могут содержать лишь цифры 0-9 и латинские буквы a-f так­ же в любом регистре: Ох9 • (9), Оха (10), восьмеричные 0xl0 - (16), 0XFFF ( 4095) с префиксом из цифры О и латинской буквы о в любом ре­ гистре. Могут содержать лишь цифры Оо7 • (7), 0ol0 0-7: (8), 0012 (10), 00777 (511) двоичные- с префиксом из цифры О и латинской буквы ь в любом регистре. Могут содержать лишь цифры О и ОЫ (1), ОЫООО (8), ОВ10011001 1: (153) Целые числа ради наглядности можно разбивать на тысячные разряды, вставляя символы подчеркивания: 524_288 ♦ (524288), вещественные • обычная - - 1_000_000 (1000000), оыоо1_1001 (двоичное 10011001) в зависимости от формы: по школьным правилам, только целая и дробная части разделя­ ются точкой (а не запятой): 6.33, 2.54, 200.8, 0.123, -3.809
Урок 4. Числа и строки 63 Если целая часть равна О, начальный ноль перед точкой можно опустить: .123 - • то же самое, что и о экспоненциальная - .123 в формате: <мантисса>е<экспонента>. Латинская буква е может быть в любом регистре: 1 le20 ( 11Х10 20 ), 2. 5Е12 (2,5 Х 10 12 ) У отрицательных чисел в начале ставится знак «минус» -1000, -0х45, -3.809, -.09013, -1.14е-7 4.1.2. (-): (-1,14х10- 7 ) Преобразование значений в числа Чтобы преобразовать значение нечислового типа в число, следует записать выра­ жение следующего формата: ♦ int (<значение> [, <основа>=lО]) - преобразует заданное значение в целое число, которое и возвращает. Если значение представлено в системе счисления, отлич­ ной от десятичной, необходимо указать основу этой системы счисления: >>>#Строка с десятичным числом >>> int ( '123') 123 >>>#Строка с шестнадцатеричным числом. Не забьrnаем указать >>>#основу системы счисления. >>> int('12af', 16) 4783 >>>#Вещественное число >>> int(123.456) 123 ♦ float (<значение>) - преобразует заданное значение в вещественное число, ко­ торое и возвращает: >>> float('56.907'), float('56'), float(56) (56.907, 56.0, 56.0) Если преобразование невозможно (например, производится попытка преобразовать список), возникает ошибка типа TypeError. Если преобразуемое число содержит недопустимые для заданной системы счисления символы (пример: якобы шестна­ дцатеричное число '12afh' ), возникает ошибка ValueError: >» int ( []) TypeError: int() argurnent must а real numЬer, not 'list' >>> int ( '12afh', 16) Ье а string, а bytes-like object or ValueError: invalid literal for int() with base 16: '12afh'
Часть 64 Язык Python: основные инструменты Обработка чисел 4.1.3. 4.1.3.1. Для /. Арифметические операции выполнения операций элементарной арифметики применяются операторы, приведенные в табл. 4.1. Таблица Оператор 1> + <слагаемое - <уменьшаемое> <вычитаемое> 1> * <множитель <делимое> / <делимое> // <делимое> % 2> <слагаемое 2> <множитель <делитель> <основание> 4.1. Арифметические операторы Python Описание Пример Результат Сложение 24 + 38 62 Вычитание 87 - 102 -15 Умножение 23 * 7 161 Деление 25 / 2 12.5 25 // 2 12 Деление нацело <делитель> <делитель> Остаток от деления 25 % 2 1 ** Возведение в степень 5 ** 2 25 <показатель> Операнды у этих операторов могут быть как целыми, так и вещественными: >>> 4 + 7.8, 9.5 * 7 (11.8, 66.5) Оператор деления / всегда возвращает результат в виде вещественного числа, даже если делятся только целые числа: >» 16 / 4 4.0 Оператор комбинированного присваивания - одновременно выполняет арифметиче­ скую операцию и присваивает ее результат переменной, в которой хранится первый операнд (табл. 4.2). 4.2. Операторы комбинированного присваивания Python Таблица Оператор Эквивалент Оператор Эквивалент а += ь а = а + ь а -= ь а = а - ь а *= ь а = а * ь а /= ь а = а / ь а %= ь а = а % ь а //= ь а = а // ь а **= ь а = а ** ь Примеры использования операторов комбинированного присваивания: >>> n = 100; n += 20; n 120 >>> n /= 6; n 20.0
Урок 4. Числа и строки 65 Оператор -<значение> меняет знак заданного значения на противоположный: >>> n = 124 >>> -n -124 >>> n = -47; -n 47 Поддерживаются четыре полезные встроенные функции (три из них знакомы нам по разд. ♦ 1.3): abs (<число>) - возвращает модуль заданного числа: >>> abs(l00), abs(-543) (100, 543) ♦ round (<число> [, <количество>=О J ) - округляет заданное число до указанного количества цифр после десятичной точки и возвращает результат. Если количество цифр равно О, округляет до целых; ♦ min (<число 1>, <число 2>, . . ., <число N>) - возвращает наименьшее из за­ . . ., <число N>) - возвращает наибольшее из за­ данных чисел; ♦ max (<число 1>, <число 2>, данных чисел. 4.1.3.2. Алгебраические и тригонометрические операции Функции для выполнения алгебраических и тригонометрических операций реали­ зованы в модуле math стандартной библиотеки. Вот некоторые из этих функций (часть из них знакома нам по разд. 1.6) и возвращаемые ими результаты: ♦ sqrt (<число>) - квадратный корень от заданного числа; ♦ cbrt (<число>) - кубический корень от заданного числа. Примеры: >>> from math import sqrt, cbrt >>> sqrt(2), cbrt(2) (1.4142135623730951, 1.259921049894873) ♦ s in (<угол>) , cos (<угол>) и tan (<угол>) - соответственно синус, косинус и тан­ генс заданного угла. Угол задается в радианах; ♦ radians (<угол в градусах>) - заданный угол, преобразованный в радианы; ♦ asin (<значение>), acos (<значение>) и atan (<значение>) - соответственно аркси­ нус, арккосинус и арктангенс заданного значения в радианах; ♦ degrees (<угол в радианах>) - заданный угол, преобразованный в градусы. Пример: >>> from math import acos, degrees >>> n = acos(0.5); n 1.0471975511965979
Часть 66 /. Язык Python: основные инструменты >>> degrees(n) 60.00000000000001 ♦ ехр (<число>) - экспонента от заданного числа; ♦ log (<число>) - натуральный логарифм от заданного числа; ♦ logl0 (<число>) - ♦ ехр2 (<показатель>) - число ♦ factorial (<число>) - факториал указанного целого числа: десятичный логарифм от заданного числа; 2 в степени, заданной показателем; >>> from math import factorial >>> factorial(l0) 3628800 ♦ floor (<число>) - ♦ ceil (<число>) - ♦ заданное число, округленное до ближайшего меньшего целого; заданное число, округленное до ближайшего большего целого; trunc (<число>) - заданное число, округленное до ближайшего меньшего целого, если число положительное, или до ближайшего большего целого, если число отрицательное. В том же модуле ♦ math имеются три полезные переменные: число п: pi - >>> from math import pi >>> pi 3.141592653589793 ♦ е- ♦ tau - число е, основание натурального логарифма; число 't, равное 4.1.3.3. 2n. Генерирование псевдослучайных чисел Для генерирования псевдослучайных чисел предназначены следующие функции из модуля ♦ random: random () 1.0: возвращает псевдослучайное вещественное в диапазоне от о. о ДО >>> from random import random >>> random() О.029512646451546387 >>> r andom ( ) О.6656604955939482 >>> random () О.3841094649523912 ♦ uniform(<нaчaлo>, <конец>) - возвращает псевдослучайное вещественное число в диапазоне от заданного начала до указанного конца: >>> from random import uniform >>> uniform(l, 10) 1.4003719395619112
Урок 4. Числа и строки 67 >>> uniform(l, 10) З.9134635927488963 >>> uniform(l, 10) З.213721184177909 ♦ то же самое, что и uniform (), только возвращает целое число: randint () - >>> frorn randorn irnport randint >>> randint(l, 10000) 1006 >>> randint(l, 10000) 2399 >>> randint(l, 10000) 2061 ♦ seed ( [ <база> J ) - задает базу, на основе которой будет генерироваться новая последовательность псевдослучайных чисел. База может быть задана в виде числа, строки, (см.разд. 4. 4). последовательности типа байтов bytes или bytearray Если база не задана, в качестве нее будет использовано текущее системное время. При одинаковых значениях базы будет выдана одна и та же по­ следовательность чисел. 4.1.4. Сравнение чисел Для сравнения чисел применяются операторы сравнения, приведенные в табл. Таблица 4.3. Операторы сравнения Python Описание Оператор 4.3. Примеры Результаты <операнд 1> -- <операнд 2> Равно 1 -- 1 1 -- 2 True False <операнд 1> != <операнд 2> Не равно 1 != 1 1 != 2 False True <операнд 1> < 1 < 2 2 < 1 1 < 1 True False False <операнд 1> <= 1 <= 2 2 <= 1 1 <= 1 False 2 > 1 True <операнд <операнд 1> > 1> >= <операнд <операнд <операнд <операнд 2> 2> 2> 2> Меньше Меньше или равно Больше Больше или равно True True 1 > 2 False 1 > 1 False 2 >= 1 True 1 >= 2 False 1 >= 1 True
Часть 68 /. Язык Python: основные инструменты Потери точности при вычислении 4.1.5. При выполнении арифметических операций с вещественными числами возможны потери точности. Это обусловлено особенностями хранения таких чисел в памяти компьютера. Например, приведенный далее код в качестве результата выдаст отнюдь не О: >>> 0.3 - 0.1 - 0.1 - 0.1 -2.7755575615628914е-17 Решить эту проблему можно, например, округляя результат до нужного знака после десятичной точки: >>> round(0.3 - 0.1 - 0.1 - 0.1, 6) -о.о 4.2. Строки Строка - это последовательность произвольных символов неограниченной дли­ ны 1 • Является объектом класса str. 4.2.1. Создание строк Для создания строки следует сделать одно из двух: ♦ записать все составляющие строку символы между одинарными или двойными кавычками: term = 'Язык программирования' language_narne = "Python" В строках, созданных одинарными кавычками, можно использовать двойные ка­ вычки, а в строках, созданных двойными кавычками, - одинарные кавычки: group_narne = 'Группа "Кино"' person_narne = "О'Брайен" Однако вставка одинарной кавычки в строку, созданную одинарными кавычка­ ми, или двойной кавычки в строку в двойных кавычках недопустима и приведет к ошибке SyntaxError. Слишком длинные строки можно разбивать на отдельные подстроки, помещая перед переводами строк символы обратного слеша >>> term = 'Язык\ программирования' >>> term 'Язык программирования' 1 Точнее, ограниченной объемом оперативной памяти. (\):
Урок 4. Числа и строки 69 Записав кавычки вплотную-'' или"", -можно создать пустую (не имеющую символов) строку; ♦ записать все составляющие строку символы между утроенными одинарными или двойными кавычками: language_name = '''Python''' Такая строка может включать одинарные и двойные кавычки и размещаться на нескольких подстроках - >>> term = причем переводы строк будут сохранены: '"'"Язык программирования "Python" >>> term 'Язык\n программирования\n 4.2.1.1. Специальные символы Специальный символ 11 "Python"\n' обозначает в строке символ, который нельзя ввести с клавиа­ туры. Так, в строке из приведенного ранее примера присутствуют специальные символы \n, обозначающие переводы строк. Наиболее часто используемые специальные символы, поддерживаемые приведены в табл. Python, 4.4. Таблица Специальный символ 4.4. Некоторые специальные символы Python Назначение \n Перевод строки \' Одинарная кавычка \" Двойная кавычка \r Возврат каретки \t Знак табуляции \u<код> Символ с заданным 16-разрядным Unicode-кoдoм Специальные символы обрабатываются лишь при выводе на экран вызовом функ­ ции print (): >>> term = >>> term 'Язык программирования\nРуthоn' 'Язык программирования\nРуthоn' >>> print (term) Язык программирования Python
Часть 70 /. Язык Python: основные инструменты Видно, что при выводе с помощью функции print () перевод строки \n был обрабо­ тан, и строковое значение term выведено на двух строчках. Еще пример: >>> formula = '\u22la2 \u2248 1.4142' >>> print(formula) ..f2.,,, 1.4142 Шестнадцатеричный Unicode-кoд 221а обозначает символ квадратного корня (✓), а код 2248 - знак приблизительного равенства (~)1. Чтобы отключить в какой-либо строке обработку специальных символов, следует предварить ее латинской буквой r в любом регистре: >>> formula = r'\u22la2 \u2248 1.4142' >>> print(formula) \u22la2 \u2248 1.4142 4.2.2. Преобразование значений в строки Любое значение можно преобразовать в строку, превратив его в объект класса str. Для этого следует записать выражение формата: str(<преобразуемое значение>) Примеры: >>> str(l234.67) '1234.67' >>> 'Python ' + str(3) 'Python 3' >>> str([l, 2, 3, 4]), str({'platform': 'Windows'}) (' [1, 2, 3, 4] ', " { 'platform': 'Windows'}") 4.2.3. 4.2.3.1. ♦ Обработка строк Извлечение символов, срезов и перебор. Цикл перебора Отдельный символ строки - можно извлечь, указав в квадратных скобках его индекс (порядковый номер). Нумерация символов начинается с О. Положитель­ ные индексы отсчитываются с начала строки, отрицательные - с конца: >>> term = 'Язык программирования' >>> term[2], term[lO], term[-1], term[-12] ( 'ы', 'а', 'я', 'р') Попытка извлечь символ по несуществующему индексу приведет к ошибке типа IndexError. 1 Автор пользовался таблицей символов Unicode, находящейся по интернет-адресу: https://symЬl.cc/en/unicode-taЫe/.
Урок ♦ 71 4. Числа и строки Срез (фрагмент строки) - записав в квадратных скобках следующую языковую конструкцию: [ <индекс 1-го символа>) символа, : [ <индекс след. за последним>) [ : <шаг>) Если не указан индекс 1-го символа, в срез попадут все символы с начала стро­ след. ки. Если не указан индекс символа, за последним, в срез попадут все сим­ волы до конца строки. Если не задан шаг, он будет принят равным 1. Примеры: • извлечение среза с 1-го по 4-й символ ( с индексами О и 3): >>> term[0:4) 'Язык' Не забываем, что вторым по счету указывается индекс не последнего символа получаемого среза, а символа, следующего за его последним символом (в на­ шем случае • 4, индекс 5-го символа строки); извлечение среза с 6-го по 16-й символ: >>> term(S: 16) 'программиро' • то же самое, только с шагом 2, в результате чего в срез попадет лишь каждый второй символ строки: >>> term[S:16:2] 'пормио' • с 4-го по 18-й символ, начиная с конца строки: >>> term[-19:-4] 'ык программиров' • два среза - с начала строки до 1О-го символа и с 13-го символа до конца строки: >>> term(:10], term[l2:) ( 'Язык прогр' , 'мирования' ) • две копии заданной строки - с прямым и обратным порядком символов: >>> term[:), term[::-1] ( 'Язык программирования' , ♦ Длину заданной строки в символах - 'яинавориммаргорп кызЯ' ) вызвав встроенную функцию len (<строка>): >>> len (term) 21 ♦ Перебрать символы строки один за другим бора, записываемого в формате: for <переменная> <тело цикла> in <строка>: - можно с помощью цикла пере­
72 Часть /. Язык Python: основные инструменты [else: <блок else>] На каждой итерации этого цикла очередной символ перебираемой строки при­ сваивается заданной переменной, и эта переменная становится доступной в теле цикла. Если цикл был выполнен полностью и не был прерван оператором break, будет выполнен блок else (в случае, когда он указан - в противном случае ничего не произойдет). Пример: >>> for s in teпn: print(s, end='-') Я-з-ы-к- -п-р-о-г-р-а-м-м-и-р-о-в-а-н-и-я- В цикле перебора можно использовать операторы перехода на следующую ите­ рацию 4.2.3.2. continue (см.разд. 3. 6.1) и прерывания break (см.разд. 3. 6. 2). Простейшие операции над строками ♦ Конкатенация (объединение) строк >>> >>> teпn = 'Язык' + -- выполняется 'программирования' с помощью оператора+: + 'Python' teпn 'Язык программирования Python' Также можно ·просто записать подстроки, подвергаемые конкатенации, через пробел >>> >>> - teпn код станет немного компактнее: = 'Язык' 'программирования ' 'Python' teпn 'Язык программирования Python' Однако с переменными, хранящими подстроки, такой номер не пройдет - воз­ никнет ошибка SyntaxError: >>> term.2 = teпn ' - это сила! ' SyntaxError: invalid syntax ♦ Повтор заданной строки указанное количество раз - оператором *. Повторяе­ мая строка записывается слева от оператора, а целочисленное количество повто­ рений - справа: >>> '-' * 30 4.2.3.3. Преобразование строк Преобразовывать строки можно следующими методами класса str: ♦ upper () - приводит все символы строки к верхнему регистру: >>> 'Python'. upper () 'PYTHON'
Урок ♦ 4. 73 Числа и строки приводит все символы строки к нижнему регистру: lower () - >>> 'Python'. lower () 'python' ♦ приводит первую букву каждого слова в текущей строке к верхнему ti tle () регистру: >>> 'python 'Python ♦ язык с большой буквы 1 '.titlе() Язык С Большой strip ( [<символы>=' \n\r']) - Буквы!' удаляет заданные символы, присутствующие в на­ чале и конце текущей строки: '.strip() Python\r\n >>> ' 'Python' Python\r\n >>> ' 'ython\r\n' ♦ split ( [sep=' ' . strip (' Pn') J [maxsplit=-1]) - 'J [, разбивает текущую строку на подстроки, сводит их в список, который и возвращает. Параметр sep задает символ, по ко­ торому выполняться будет (по разбиение умолчанию: пробел). Параметр maxsplit указывает количество подстрок, которое будет присутствовать в воз­ вращаемом списке. Если задать значение -1, в список попадут все подстроки. Если подстрок получится больше, чем задано в этом параметре, в список будет добавлен еще один элемент, содержащий остаток текущей строки. Обработка текущей строки производится от начала к концу: >>> 'l 2 3 4'.split(), '1 2 3 4'.split(maxsplit=2) {['1', '2', '3', '4'], ['1', '2', '3 4']) >>> '1, 2, 3, 4'.split(sep=', ') [, l', , 2,, , 3,, , 4,] >>> '1, 2, [ 11', ♦ rspli t () - З, 121, maxsplit=2) ' 4' .split (sep=', 4 1] 1 3, то же самое, что и spli t (), только текущая строка обрабатывается от конца к началу: >>> '1 2 3 4'.rsplit(maxsplit=2) 2 1 , '3 1, 1 4 1] [ 11 >>> 'l, 2, 3, 4'. rsplit {sep=', [, l, 2 ,, , 3 ,, , 4 ,] ♦ splitlines ( [keepends=False]) символам перевода строки параметру keepends будут, если True - >>> 'Python\nYpoк ['Python', >>> maxsplit=2) ' разбивает текущую строку на подстроки по \n и сводит их в список, который и возвращает. Если дать значение False, символы \n в подстроки включены не будут: 1\nВведение' 'Урок 1', .splitlines() 'Введение'] 'Python\nYpoк l\nBвeдeниe'.splitlines(keepends=True) ['Python\n', 'Урок l\n', 'Введение']
Часть 74 Язык Python: основные инструменты Поиск и замена в строках 4.2.3.4. ♦ /. <подстрока> in <строка> - оператор вхождения возвращает True, если за­ - данная подстрока присутствует в указанной строке, и False - в противном слу­ чае: >>> 't' in 'Python', 'yth' in 'Python', 'we' in 'Python' (True, True, False) ♦ <подстрока> not in <строка> - оператор невхождения- возвращает True, если заданная подстрока отсутствует в указанной строке, и False - в противном слу­ чае: >>> 't' not in 'Python', 'yth' not in 'Python', 'we' not in 'Python' (False, False, True) ♦ find () - ищет в текущей строке заданную подстроку и возвращает индекс сим­ вола, с которого начинается ее вхождение. Поиск производится от начала к кон­ цу текущей строки. Формат вызова: find(<подстрока>[, <индекс первого символа>[, <индекс символа, следующего за последним>] ]) Пример: >>> I длина ДЛИННЫЙ удлинять I , find ( I длин I) о Если заданы индексы, из текущей строки извлекается срез (см.разд. 4.2.3.1), и поиск производится в нем: >>> 1 длина ДЛИННЫЙ удлинять 1 • f ind ( 1 ДЛИН 1 , 4, 14) 6 Если подстрока не найдена, возвращается >>> -1: 'длина длинный удлинять'.find('корот') -1 ♦ rfind () - то же самое, что и find (), только поиск производится от конца к на­ чалу текущей строки: >>> 'длина длинный удлинять'. rfind ('длин') 15 ♦ replace () - заменяет в текущей подстроке вхождения заменяемой подстроки на заменяюшую и возвращает результат: rерlасе(<заменяемая подстрока>, <заменяющая подстрока>[, count=l]) Пример: >>> 'int int int'.replace('int', 'float') 'float float float' Параметр count задает количество вхождений заменяемой подстроки, которые следует заменить. Если указать значение-!, заменены будут все вхождения:
Урок 4. 75 Числа и строки >>> 'int int int'.replace('int', 'float', count=2) 'float float int' 4.2.4. Сравнение строк Строки можно сравнивать, применяя те же операторы, которые используются для сравнения чисел (см. табл. 4.3): >>> lang = 'Python' 'JavaScript' >>> lang == 'Python', lang (True, False) >>> lang != 'Python', lang != 'JavaScript' (False, True) При сравнении строк фактически сравниваются коды составляющих их символов. Сначала сравнению подвергаются коды первых символов каждой из сравниваемых строк. Если первые символы одинаковы, сравниваются коды вторых символов и т. д. Вот несколько примеров: >>>#Код латинской буквы >>> < 'а' "а" меньше кода латинской буквы "а" буква >>>#Соответственно, "меньше" буквы "Ь". "Ь". 'Ь' True >>>#Если первые >>>#вторые >>> 'ааа' строк совпадают, сравниваются д. символы и т. < 'аЬ', (True, True) 'аа' 4.2.5. символы сравниваемых < 'ааЬ' Форматируемые строки Форматируемая строка - содержит команды на вставку в соответствующие места строки значений указанных переменных и (или) результаты вычисления заданных выражений. Форматируемая строка предваряется префиксом в виде латинской буквы f в любом регистре. Команда на вставку значения записывается непосредственно в составе формати­ руемой строки в следующем формате: (<переменная или выражение>[:<описание формата вставляемого значения следует записывать в таком виде: Описание [<заполнитель>=' [,] [ ] [. формата>]) ' [<выравнивание>='>' ] ] [<знак>=' - ' ] [ #] [О] [ z] [ <ширина>=О] ~ <точность>=б] [ <формат вывода>=' s'] В качестве формата вывода можно указать: ♦ s- обычная строка. Значение выводится без каких бы то ни было преобразо­ ваний:
76 Часть /. Язык Python: основные инструменты >>> lang = 'Python' >>> version = 3 >>> f'Язык {lang:s}, Python, 'Язык ♦ версия версия {version}' 3' d- целое число в десятичной системе счисления: >>> >>> nшn = 1234567890 f'Число 'Число {nшn:d}' 1234567890' Тысячные разряды можно разделять запятыми или символами подчеркивания, поместив соответствующий символ перед значком формата d: >>> ♦ х - f'Число {nшn:,d}', ('Число 1,234,567,890', f'Число {nшn:_d}' 'Число 1_234_567_890') целое число в шестнадцатеричной системе счисления. Чтобы перед числом выводилось обозначение Ох, перед значком формата х следует поместить символ решетки(#): >>> - ♦ х ♦ о - f'Число {nшn:x}', ('Число 499602d2', f'Число 'Число (nшn:#x}' Ox499602d2') то же самое, что и х, только будет выводиться обозначение ох; целое число в восьмеричной системе счисления. Чтобы перед числом вы­ водилось обозначение Оо, перед значком формата о следует поместить символ решетки(#): >>> ♦ ь - {nшn:o} ', f'Число {nшn: #о)' ( 'Число 11145401322', 'Число 0011145401322') f'Число целое число в двоичной системе счисления. Чтобы перед числом выводи­ лось обозначение оь, перед значком формата ь следует поместить символ решет­ ки(#); ♦ f или F - >>> >>> nшn вещественное число в обычной форме: = 1234.56789012 f'Число {nшn:f}', ('Число 1234.567890', f'Число {nшn:F}' 'Число 1234.567890') ♦ е- вещественное число в экспоненциальной форме с буквой е. ♦ Е- вещественное число в экспоненциальной форме с буквой Е. Пример: >>> ♦ g - f'Число (nшn:e}', ('Число 1.234568е+03', f'Число 1.234568Е+03') вещественное число в обычной или экспоненциальной форме с буквой е - какая короче: = О.00000123456789 >>> nшn2 = 3.4 >>> (nшn:E}' 'Число nшn
Урок 4. >>> {num:g}', f'Число {nшn2:g}' l.23457e-06', 'Число 3.4') f'Число {'Число ♦ G- 77 Числа и строки вещественное число в обычной или экспоненциальной форме с буквой Е - какая короче: ♦ %- вещественное число, умноженное на 100, с символом процента в конце: >>> num = .035 >>> f' Доля: {num: %} ' 'Доля: 3.500000%' Остальные составные части приведенной команды: ♦ ширина - минимальная ширина поля, в котором будет выведено значение, в символах: >>> num = 1234; nшn2 = 1234567890.123456; lang = 'Python' >» f' 1{lang:10} 1 1{num:l0d} 1 1 {nшn2:l0f} 1' 1234 1 11234567890.1234561' 1 1 ' 1Python ♦ точность - у вещественных чисел - количество цифр после десятичной точки: >>> num = 12.34567890123 >>> f' 1{num:l4f} 1 1{num:14.l0f} 1 1{num:14.2f} 1' 12.3456791 1 12.34567890121 1 '1 12.351' При округлении отрицательных чисел может получиться отрицательный ноль. Превратить его в обычный, положительный, можно, вставив перед шириной (если она указана) или точностью букву z. Пример: >>> num = -0.00123456789 >>> f'l{num:l4.2f}I l{num:zl4.2f)I' ' 1 ♦ выравнивание - -0.001 О. 1 00 1 ' выводимого значения внутри поля заданной ширины. У всех форматов вывода доступны для указания значения выравнивания < (по левому краю),> (по правому краю) ил (по центру): »> f'l{lang:10}1 ' 1Python l{lang:<10}1 1 1Python l{lang:>10}1 1 1 l{lang:лl0}I' Python 1 1 Python 1' У числовых форматов также можно указать выравнивание =, тогда знак числа будет выровнен по левому краю, а само число - по правому: >>> nшn2 = -1234 >>> f' 1{num:=l0d} 1 1 {nшn2:=l0d} 1' 1234 1 11234 1 ' '1 По умолчанию пространство между знаком и числом заполняется пробелами. Чтобы оно заполнялось нулями, следует поместить цифру О перед значением ширины. »> f' 1 {nшn2:=l0d} 1 1 {nшn2:=010d} 1' 1234 1 1-000001234 1 ' ' 1-
78 ♦ Часть заполнитель - /. Язык Python: основные инструменты символ, которым будет заполняться свободное пространство в поле заданной шириньt. >>> lang = 'Python' »> f'l{lang:лlO}I ' 1 ♦ знак - Python l{lang:*лlO}I l{lang:-лlO}I 1 1* * Python * * 1 1-- Python-- 1 1 l{lang:_лlO}I' Python_ 1 ' управляет выводом знака у чисел. Доступные для указания знаки: - (знак выводится только у отрицательных чисел), + (выводится всегда) и пробел (у по­ ложительных чисел выводится пробел, а у отрицательных - минус): >>> num = 1234; num2 = -1234 >» f' 1{num:d} 1 1{num2:d} 1 1{num:-d} 1 1{num2:-d} 1' '112341 1-12341 112341 1-12341' »> f' 1{num:+d} 1 1{num2:+d} 1 1{num: d} 1 1{num2: d} 1' '1+12341 1-12341 1 12341 1-12341' Перепишем программу из листинга в 10°, 3.3, выводящую синусы углов 0-360° с шагом таким образом, чтобы она округляла синус до 4-й цифры после точки. Для оформления вывода нужным образом применим форматированную строку. Код исправленной программы приведен в листинге 4.1. frorn rnath irnport sin, radians deg = О while deg <= 360: r = radians(deg) print(f'sin({deg:OЗd}): {sin(r} :6.4f}') deg += 10 Вывод этой программы: sin (ООО) : sin(OlO): sin (020) : 0.0000 0.1736 0.3420 sin(l90): -0.1736 sin(200): -0.3420 sin (210): -0. 5000 4.3. Последовательности Строка - это пронумерованная неизменяемая последовательность. Последователыюсть 11 значение, представляющее собой упорядоченный набор дру­ гих значений (элементов).
Урок 4. 79 Числа и строки Пронумерованная последовательность - позволяет обратиться к любому элементу по его порядковому номеру (индексу), непронумерованная последовательность не позволяет это сделать. У изменяемой последовательности можно менять значения элементов, добавлять и удалять элементы, у неизменяемой последовательности - нельзя. Попытка изменить элемент любой неизменяемой последовательности (например, строки) приведет к ошибке типа TypeError: >>> lang = 'Pytyon' >>> lang[3] = 'h' TypeError: 'str' object does not support itern assignrnent 4.4. Что еще нужно знать о строках и не только ♦ Изменить отдельный символ в строке всё же можно. Для этого следует взять два среза: от начала строки до символа, предшествующего изменяемому, и от сле­ дующего символа до конца строки. После чего останется выполнить конкатена­ цию первого среза, нового символа и второго среза. Пример: >>> lang = 'Pytyon' >>> lang[:3] + 'h' + lang[4:] 'Python' ♦ Выражения в форматируемых строках можно разделять на подстроки: рrint(f'Результат: 2 + ** 2 + ** 2} ') {а** Ь с ♦ В форматируемых строках поддерживаются команды формата: {<переменная>= } Такая команда вставляет в строку имя заданной переменной, символ равенства и значение переменной. Пример: >>> nurn = 123 >>> f'Число {nurn = }' 'Число nurn = 123' Обычно такие команды применяют при отладке программ для контроля значе­ ний переменных. ♦ Класс bytes представляет неизменяемую пронумерованную последовательность байтов. Она записывается так же, как и строка, только с префиксом в виде бук­ вы ь в любом регистре. Отдельные байты могут быть записаны либо в виде обычных символов кодовой таблицы \х<шестнадцатеричный код байта> ASCII, либо в формате:
80 Часть /. Язык Python: основные инструменты Пример записи двух одинаковых строк в разных форматах: >>> >>> >>> Ы = b'Python' Ь2 = Ь'\х50\х79\х74\х68\хбf\хбе' == Ь2 Ы True Объект последовательности bytes может быть создан на основе заданной после­ довательности чисел выражением формата: Ьуtеs(<последовательность чисел>) В качестве последовательности можно указать, например, список (см.разд. или кортеж (см.разд. до 255 - >>> >>> Ь2 1.5.4). любое другое значение приведет к ошибке типа = bytes([Ox50, Ох79, 1.5.3) В ней могут присутствовать лишь целые числа от О Ох74, ОхбВ, Охбf, ValueError: Охбе]) Ь2 b'Python' Объект последовательности байтов bytes можно также создать на основе задан­ ных строки и кодировки, записав выражение формата: bytes (<строка> [, <кодировка>] ) Пример: >>> >>> ЬЗ = bytes ('Язык', 'utf-8') ьз b'\xd0\xaf\xd0\xЬ7\xdl\x8b\xd0\xba' Метод hex ( ) объекта типа bytes возвращает шестнадцатеричное представление последовательности байтов в виде строки: »> Ь2 .hex () '507974686fбе' Преобразовать заданную последовательность байтов в строку можно с помощью вот такого выражения: str(<последовательность байтов>, <кодировка>) Примеры: >>> str (Ы, 'ascii') , str (ЬЗ, 'utf-8') ( 'Python', 'Язык') ♦ Класс bytearray аналогичен классу bytes, только является изменяемым. Его объ­ ект может быть создан на основе заданной последовательности чисел или строки выражением одного из двух форматов: Ьуtеаrrау(<последовательность bytearray(<cтpoкa>, чисел>) <кодировка>) Примеры: >>> >>> Ы = bytearray([Ox50, ы bytearray(b'Pytyon') Ох79, Ох74, Ох79, Охбf, Охбе])
Урок 4. 81 Числа и строки >>>#Заменяем один из элементов >>> >>> Ы[3] = последовательности байтов Ох68 ы bytearray(b'Pythoп') >>> Ь2 = Ьуtеаrrау('Язык', 'utf-8') >>> str (Ы, 'ascii') , str (Ь2, 'utf-8') ( 'Pythoп', 'Язык') Последовательности байтов - весьма специфический тип данных, применяе­ мый лишь при работе с двоичными файлами, сетевыми интерфейсами и внут­ ренними механизмами операционной системы. 4.5. 1. Самостоятельные упражнения Напишите программу «Кости», которая при каждом запуске будет выдавать два псевдослучайных целых числа от 2. 1 до 6. Напишите новую версию программы из листинга 4.1, выводящей синусы углов, которая будет отображать данные в три колонки: sin(000): sin(030): sin(060): 0.0000 0.5000 0.8660 sin(0l0): sin(040): sin(070): Подумайте, как это сделать. 0.1736 0.6428 0.9397 sin(020): sin(0S0): sin(080): 0.3420 0.7660 0.9848
Урок5 Списки, кортежи, генераторы и словари 5.1. Списки Список это пронумерованная изменяемая последовательность. Является объек­ - том класса 5.1.1. list. Создание списков Создать список можно: ♦ указав элементы будущего списка через запятую и заключив их в квадратные скобки: >>> types = ['int', 'folat', 'str', 'bool'] Записав квадратные скобки вплотную - [ J, - мы создадим пустой (не имею­ щий элементов) список; ♦ создав объект класса list на основе другой последовательности с помощью вы­ ражения формата: list ( [<последовательность>]) Для примера создадим на основе строки ностью - см. разд. 4.3) (она также является последователь­ список, содержащий отдельные символы этой строки: >>> letters = list('Pandas') >>> letters ['Р', 'а', 'n', 'd', 'а', 's'] Если последовательность не указана, будет создан пустой список. Списки могут содержать элементы любых типов, включая другие списки: >>> 1st = [ (1, 2, 3), (4, 5, 6), (7, 8, 9)] 5.1.2. Обработка списков 5.1.2.1. Простейшие операции над списками Поскольку список является пронумерованной последовательностью, он поддержи­ вает все операции, характерные для последовательностей: обращение к элементу по
Урок 83 Списки, кортежи, генераторы и словари 5. его индексу, извлечение среза, получение размера (количества элементов в списке) и перебор элементов: >>>#Обращаемся к элементам списка по их индексам >>> types(2], types(-1] ('str', 'bool') >>>#Извлекаем срезы >>> types[0:3], types(3:1:-1] (['int', 'folat', 'str'], ['bool', 'str']) >>>#Получаем размеры списков >>> len(types), len(letters) (4, 6) >>>#Перебираем элементы списка в цикле >>> for l in letters: print(l.upper(), end=' Р N D А А ') S Поскольку список является изменяемой последовательностью, мы можем изменять значения его элементов: >>>#Исправим название второго типа данных из списка types >>> types(l] = 'float' >>> types ('int', 'float', 'str', 'bool'] При обращении к элементу с несуществующим индексом возникнет ошибка типа IndexError. Чтобы обратиться к элементу списка, вложенного в другой список, следует указать две пары квадратных скобок и записать в первой паре индекс самого вложенного списка во «внешнем» списке, а во второй паре - индекс нужного элемента во вло­ женном списке: »> 1st (2] (О] 7 Можно объединить два списка, применив оператор +. В результате будет возвращен новый список. Пример: >>> letters = letters + (' ', '2'' >>> letters [ 'Р', 'а', 'n', 'd', 'а', 's', 1 1 '2 1] 1 1 '2 1, . , '2'] Другой способ объединить списки: >>> >>> а = (1, 2, 3]; (*а, Ь = (4, 5, 6] *Ь] (1, 2, 3, 4, 5, 6] >>> (*Ь, *а] (4, 5, 6, 1, 2, 3] Можно повторить заданный список указанное количество раз, использовав опера­ тор *. В результате также будет возвращен новый список:
84 Часть /. Язык Python: основные инструменты >>> [1, 2, 3] * 3 [1, 2, 3, 1, 2, 3, 1, 2, 3] Кроме того, поддерживаются операторы комбинированного присваивания += и *=. 5.1.2.2. Добавление и удаление элементов списка Для удаления ненужного элемента списка следует применять оператор удаления del: del <обращение к удаляемому элементу> Пример: >>> del letters[9]; del letters[8] >>> letters ['Р', 'а', 'n', 'd', 'а', 's', 1 1 121] Для добавления и удаления элементов списка можно использовать следующие ме­ тоды класса ♦ list: append (<значение>) - добавляет заданное значение в конец текущего списка. Результата не возвращает: >>> types.append('list') >>> types ['int', 'float', 'str', 'bool', ♦ extend (<последовательность>) - 'list'] добавляет элементы заданной последовательно­ сти в конец текущего списка. Результата не возвращает: >>> types.extend(('tuple', 'range', 'dict')) >>> types ['int', 'float', 'str', 'bool', 'list', 'tuple', 'range', 'dict'] >>> letters.extend('-3.0-') >>> letters ['Р', 'а', 'n', 'd', 'а', 's', ' ' , '2', '-', '3', '.', '0', '-'] Этот метод может принимать последовательности разных типов, в частности кортежи и строки (что и было показано в приведенном примере). В то время как оператор + работает лишь со списками; ♦ insert (<индекс>, <значение>) - вставляет заданное значение в текущий список по указанному индексу. Результата не возвращает: >>> letters.insert(8, >>> letters ['Р', 'а', '.') 'n', 'd', 'а', 's', ' ' '2', '-' 'з', '0', '-'] ♦ рор ( [ <индекс> J ) - удаляет из текущего списка элемент с указанным индексом и возвращает его значение. Если индекс не указан, удаляет последний элемент. Если заданный индекс не существует или список пуст, возникает ошибка типа IndexError:
Урок 5. Списки, кортежи, генераторы и словари 85 >>> letters.pop(); letters.pop(9) ,_, '-' >>> letters ['Р', ♦ clear () - 'а', 'n', 'd', 's', ' ' , '2', '.', 'а', 'З', '.', 'О'] очищает текущий список от всех элементов. Результата не возвра­ щает. 5.1.2.3. Обработка элементов списка Три встроенные функции помогуr при обработке списков и прочих последователь­ ностей, содержащих только числа (две из них нам уже знакомы и по разд. 1.3 4.1.3.1): ♦ min (<последовательность>) - возвращает наименьший элемент заданной после­ довательности; ♦ max (<последовательность>) - возвращает наибольший элемент заданной после­ довательности. Если любой из этих двух функций передать пустую последовательность, воз­ никнет ошибка типа ValueError; ♦ sиm(<последовательность>) - возвращает сумму элементов заданной последова- тельности. Метод join (<последовательность>) класса str объединяет все элементы заданной последовательности в строку и возвращает ее в качестве результата. Значение теку­ щей строки будет использов~що в качестве символа-разделителя. Элементы после­ довательности должны быть сv>Ь'ками - иначе возникнет ошибка типа TypeError: >>> ', '. join (types) 'int, float, str, bool, list, tuple, range, dict' >>> ''.join(letters) 'Pandas 2.3.0' Напишем программу (листинг 5.1 ), которая будет получать от пользователя числа, выводить арифметическое среднее сводить их в список, а после ввода команды q - введенных чисел . Также на случай ошибки ввода предусмотрим удаление послед­ него из занесенных чисел - lhlcnntr 8.1. ПporJNUOl8 дпя по вводу команды d. вычисления среднеrо арифметмческоrо 888Д8ННЫХ чмсеn print('Pacчeт среднего арифметического введенных чисел.') print ( 'Допустимый ввод:') print(' * очередное число print(' * команда q print (' * команда d values [] заносится в список введенных;') выводит результат и завершает программу;') удаляет последнее введенное число.')
Часть 86 while True: input_value = inрut('Введите число или if input_value == 'q': avg = sum(values) / len(values) рrint('Рассчитанное среднее:', avg) break elif input_value == 'd': values. рор () else: values.append(float(input value)) input ( 'Нажмите <Enter> для /. Язык команду: Python: основные инструменты ') завершения программы') Здесь мы создаем пустой список для вводимых чисел и присваиваем его перемен­ ной values. Далее в бесконечном цикле запрашиваем у пользователя числа и команды. Если была введена команда q, вычисляем сумму всех чисел, хранящихся в списке values, и делим его на размер этого списка, тем самым получая арифметическое среднее. Далее выводим среднее на экран и прерываем цикл ввода чисел. Если была введена команда d, удаляем последний элемент списка values вызовом метода рор (). Результат, возвращаемый этим методом, нам не нужен, и мы его никуда не заносим. В противном случае предполагается, что пользователь ввел очередное число. Пре­ образуем его в вещественный тип и добавляем в список values вызовом метода append (). 5.1.2.4. Поиск элементов в списках Для проверки, существует ли в списке элемент с заданным значением, можно использовать операторы вхождения in и невхождения not in (см. разд. 4.2.3.4): >>> 'bool' in types, 'empty' in types (True, False) >>> 'bool' not in types, 'empty' not in types (False, True) Метод index () класса list ищет в текущем списке элемент с заданным значением и возвращает его индекс: indех(<значение>[, <индекс первого элемента>[, <индекс элемента, Пример: >>> digits = [l, 3, 7, 4, 8, 1, 7] >>> digits.index(7) 2 следующего за последним>]])
Урок 5. Списки, кортежи, генераторы и словари 87 Если заданы индексы, из текущего списка извлекается срез, и поиск производится в нем: >>> digits.index(7, 3, 7) 6 Если элемент с заданным значением в текущем списке не найден, возникнет ошиб­ ка типа ValueError: >>> digits.index(5) ValueError: 5 is not in list Обработка ошибок будет рассмотрена на уроке 5.1.2.5. 9. Выдача элементов списков, выбранных псевдослучайным образом Для этого следует использовать следующие функции из модуля randorn: ♦ choice (<последовательность>) - возвращает элемент заданной последователь­ ности, выбранный псевдослучайным образом. Эта функция знакома нам по разд. ♦ 1.6; sarnple (<последовательность>, <количество>) - возвращает список из указанно­ го количества элементов заданной последовательности, выбранных псевдослу­ чайным образом: >>> frorn randorn irnport sarnple >>> languages = ['Python', 'JavaScript' , >>> sarnple(languages, 3) ['Python', 'РНР', 'Java'] >>> sarnple(languages, 3) ['С++', 'JavaScript', 'Python'] Напишем программу (листинг 5.2), 'RuЬy', 'РНР', 'Java', 'С++'] которая при каждом запуске будет возвращать шуточный род занятий, наподобие «Разработка удочек в Камбодже». frorn randorn irnport choice wordsl ['Разработка', 'Программирование', 'Возведение', words2 words3 ['шарниров', 'удочек', ['в Парагвае', 'на 'сараев', даче', 'Проектирование', 'Утилизация'] 'тачек', 'в Камбодже', 'носовых платков'] 'на дому', print(choice(wordsl), choice(words2), choice(words3)) input('Дnя завершения программы нажмите <Enter>') 'на Марсе']
Часть 88 /. Язык Python: основные инструменты 5.1.З. Сравнение списков Списки можно сравнивать, применяя операторы сравнения == и ! =. Списки счита­ ются равными, если содержат одинаковый набор элементов, расположенных в оди­ наковом порядке: >>> [1, 2] == [1, 2], [1, 2] (True, False, False) 5.1.4. [1, 2] [2, 1] Списковые включения Списков~е включение 11 [1], выражение, создающее список на основе элементов заданной исходнои последовательности. Поддерживаются два формата списковых включений: простой и расширенный. Вот простой формат: [ <выражение> for <переменная> in <исходная последовательность>] Списковое включение перебирает исходную последовательность, присваивая ее оче­ редной элемент указанной переменной. ВЫражение производит заданные вычисления над элементом из переменной, и результат добавляется в создаваемый список, кото­ рый и возвращается в качестве результата. Пример спискового включения, которое на основе исходного списка целых чисел создает другой список, содержащий квадраты этих чисел: >>> nшnЬers [32, 6, 81, 9, 13] >>> squares [n ** 2 for n in nwnЬers] >>> squares [1024, 36, 6561, 81, 169] Расширенный формат спискового включения: [ <выражение> for <переменная> in <исходная последоват.> if <условие>] Здесь обрабатываются лишь те элементы исходной последовательности, в отноше­ нии которых заданное условие станет истинным. Получить доступ к очередному элементу исходной последовательности в коде условия можно посредством заданной переменной. Пример аналогичного спискового включения, которое возвращает список квадра­ тов только нечетных чисел: >>> [n ** 2 for n in [6561, 81, 169] Описанные (см.разд. nшnЬers здесь действия 4.2.3.1). if n % 2 можно выполнить и с помощью цикла перебора Однако списковое включение компактнее и значительно быстрее выполняется 1 . 1 1] Хотя, на взгляд автора, цикл нагляднее.
Урок 5. 5.2. Списки, кортежи, генераторы и словари 89 Кортежи Кортеж - это неизменяемый список. Он является объектом класса tuple. Кортежи занимают меньше памяти, чем списки, и обрабатываются немного быст­ рее. 5.2.1. Создание кортежей Создать кортеж можно двумя способами: ♦ указав элементы будущего кортежа через запятую и заключив их в круглые скобки: >>> types = ('int', 'float', 'str', 'bool') Если создаваемый кортеж должен содержать лишь один элемент, после него обязательно следует поставить запятую (иначе исполняющая среда посчитает, что это не кортеж, а просто единичное значение в скобках): >>> one_element_tuple = (123,) Можно вообще убрать круглые скобки, сохранив, однако, запятые: >>> types = 'int', 'float', 'str', >>> one element_tuple = 123, 'bool' Однако кортежи без скобок проигрывают в наглядности. Записав квадратные скобки вплотную ♦ - (), - можно создать пустой кортеж; создав объект класса tuple на основе другой последовательности с помощью вы­ ражения формата: tuple ( [<последовательность>] ) Пример: >>> letters >>> letters ('N', tuple ( 'NumPy') 'u', 'm', 'Р', 'у') Если последовательность не указана, будет создан пустой кортеж. Кортежи могут содержать элементы любых типов, включая списки и другие кортежи: >>> tpl = 5.2.2. Работа с кортежами ( (1, 2, 3), [4, 5, 6], (7, 8, 9)) Над кортежами можно выполнять те же действия, что и над списками, те, которые не изменяют их содержимое: >>>#Обращение к элементам по их индексам >>> types[3], letters[-1] ('bool', 'у') - но лишь
Часть 90 /. Язык Python: основные инструменты >>>#Извлечение срезов >>> types[:2], letters[3:] (('int', 'float'), ('Р', 'у')) >>>#Получение размеров >>> len(one elernent tuple), len(letters) ( 1, 5) >>>#Перебор элементов >>> for t in types: print(t, end=', ') int, float, str, bool, >>>#Сложение кортежей >>> types + letters ('int', 'float', 'str', 'bool', 'N', 'u', 'rn', >>>#Повторение 'Р', 'у') кортежа >>> letters * 2 ('N', 'u', 'm', 'р', 'у', 'N', 'u', 'm', 'Р', 'у') Операции сложения и повторения не изменяют содержимого кортежей, поэтому допустимы. Функции rnin () , rnax () , surn () (см.разд. дения not in (см. разд. >>> >>> nurnЬers 4.2.3.4) 5.1. 2. 3), операторы вхождения in и невхож­ прекрасно обрабатывают кортежи: = 4, 7, 23, 9, 90, 2, -5 rnin(nurnЬers), rnax(nurnЬers), (-5, 90, 130) >>> 7 in nurnЬers, 90 not in (True, False) surn(nurnЬers) nurnЬers Класс tuple, представляющий кортеж, поддерживает метод index () (см.разд. >>> 5.1.2.4): nurnЬers.index(-5) 6 Однако любая попытка изменить кортеж приведет к ошибке типа TypeError: >>> nurnЬers[2] = 32 TypeError: 'tuple' object does not support itern assignrnent 5.2.3. Именованные кортежи Именованный кортеж - кортеж, к элементам которого можно обращаться не только по целочисленным индексам (которые легко забыть), а еще и по произвольно задан­ ным строковым именам. Класс именованного кортежа (шаблон, на основе которого создаются отдельные экземпляры именованного кортежа) создается и возвращается функцией narnedtuple () из модуля collections стандартной библиотеки: narnedtuple(<имя создаваемого класса>, <имена элементов>[, defaults=None])
Урок 5. Списки, кортежи, генераторы и словари Имя класса задается в виде строки, имена 91 - элементов в виде последовательности из строк. Имена можно выбрать произвольно, однако они должны быть уникальными в пределах создаваемого кортежа и удовлетворять требованиям к именам перемен­ ных (см.разд. 1.4). В параметре defaul ts можно указать последовательность значений по умолчанию для элементов создаваемых кортежей. Эти значения назначаются элементам, начи­ ная ние с - конца: последнее значение - последнему элементу, предпоследнее значе­ предпоследнему элементу и т. д. Класс именованного кортежа, возвращенный функцией namedtuple (), следует со­ хранить в какой-либо переменной. В качестве примера создадим класс именованного кортежа Point, который будет хранить сведения об экранном пикселе в элементах с именами: х (горизонтальная координата), у (вертикальная координата), color (цвет, по умолчанию т. е. черный) и is _ visiЫe (видимость, по умолчанию - - охоооооо, тrue, т. е. видимый): >>> frorn collections irnport namedtuple >>> Point = namedtuple('Point', ('х', 'у', 'color', 'is_visiЬle'), defaults=(0x000000, True)) Для создания именованного кортежа этого класса достаточно вызвать полученную функцию, передав ей с именованными параметрами значения соответствующих элементов: >>> pointl = Point(x=l00, у=200) >>> pointl Point(x=l00, у=200, color=0, is_visiЬle=True) Point(x=400, у=ЗОО, is_visiЬle=False) >» point2 >>> pointЗ Point(x=1024, у=768, color=0x33ff55) Обращаться к элементам именованного кортежа можно как по индексам, так и по именам: >>> pointl[0), pointl[l), point1[2], pointl[З] (100, 200, О, True) point2.x, point2.y, point2.color, point2.is (400, 300, О, False) Именованные кортежи - visiЫe удобная и занимающая меньше памяти альтернатива полноценным классам (рассмотрены на уроке 7). Однако именованные кортежи подходят лишь для хранения каких-либо сложных сущностей - если же их необ­ ходимо еще и обрабатывать, придется писать классы. 5.3. Распаковка последовательностей Распако~ка (или позиционное присваивание) 11 это присваивание разных элементов заданнои последовательности разным переменным в одном выражении: <переменная 1>, <переменная 2>, . . . , <перем. N> = <последовательность>
Часть 92 /. Язык Python: основные инструменты Можно указать последовательность любого вида: строку, список или кортеж. Примеры: >>> types = ('int', >>> tl, t2, 'float', 'str', 'bool') bool t = types tЗ, >>> tl 'int' >>> t2 'float' >>> tЗ 'str' >>> bool t 'bool' >>> а, ь, с >>> а, ь, с ('а', = 'аЬс' 'Ь', 'с') Количество указанных переменных должно совпадать с количеством элементов в заданной последовательности- иначе возникнет ошибка типа ValueError: >>> tl, t2, tЗ = types ValueError: too many values to unpack (expected 3) Однако всё же можно записать переменных меньше, чем элементов в последователь­ ности, но перед одной из переменных необходимо поставить символ звездочки ( *). Тогда переменной, помеченной звездочкой, будет присвоен список с <<Лишнимю> элементами последовательности: >>> tl, t2, *tЗ >>> tl, t2, tЗ = types ( 'int', 'float', [ 'str', 'bool']) >>> tl, *t2 = types >>> tl, t2 ( 'int', ['float', 1 str', 'bool']) Если количество переменных равно размеру последовательности, в переменной со звездочкой окажется список из единственного элемента: >>> tl, t2, tЗ, >>> tl, t2, tЗ, *t4 = types t4 ('int', 'float', 'str', ['bool']) А если переменных на одну больше >>> tl, t2, tЗ, >>> tl, t2, tЗ, - пустой список: t4, *t5 = types t4, t5 ('int', 'float', 'str', 'bool', [])
Урок 93 Списки, кортежи, генераторы и сповари 5. 5.3.1. Пара слов о множественном присваивании и множественном выводе В разд. d, с, >>> 1.4 мы е рассмотрели множественное присваивание вида: = 20, 100, 4.82 Фактически присваиваемые значения, записанные через запятую слева от операто­ ра присваивания, формируют кортеж. Который сразу же распаковывается в пере­ менные, записанные слева. Аналогично работает и множественный вывод. Выводимые значения, записанные через запятую, создают кортеж, который выводится исполняющей средой как кор­ теж - в круглых скобках: >>> с, d, е (20, 100, 4. 82) 5.4. Генераторы Генератор 11 программный и~струмент, формирующий последовательность по заданному алгоритму и выдающии ее по запросу элемент за элементом. Генератор может выдавать, например, целые числа, находящиеся в заданном диапа­ зоне, с указанным шагом между ними. Генератор не хранит все элементы последовательности в памяти, а вычисляет каж­ дый элемент непосредственно в момент запроса. Вследствие чего он занимает зна­ чительно меньший объем памяти, нежели список или кортеж того же размера. 5.4.1. 11 Генераторы-функции Генератор-функция - генератор, реализованный в виде функции. Наиболее часто применяются следующие генераторы-функции: ♦ range () - выдает диапазон. Диапазон 11 последовательность целых чисел, расположенных от начального ния включительно до конечного значения исключительно, с указанным шагом. Формат вызова функции: range ( [ <начальное значение>=О, ] <конечное значение> [, <шаг>=l] ) Не забываем, что конечное значение в выдаваемый диапазон не входит. Примеры: >>> # Создаем диапазон от 1 >>> rngl = range(l, 11) >>> # >>> # Преобразуем его какие в числа в него ДО список, входят 10 чтобы посмотреть, значе-
Часть 94 /. Язык Python: основные инструменты >» list (rngl) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> rng2 = range(l0, О, -1) >» list (rng2) (10, 9, 8, 7, 6, 5, 4, 3, 2, 1] Можно обращаться к элементам диапазона по их индексам, получать срезы (в результате возвращается новый диапазон), узнавать размер диапазона (коли­ чество элементов в формируемой последовательности). К диапазонам можно применять операторы вхождения и невхождения, функции min ( ) , max ( ) и sшn ( ) , операторы сравнения rng2[-2] >>> rngl[4], (5, == и ! =. Диапазоны поддерживают метод index () : 2) >>> 2 in rngl, 22 in rng2, 5 not in rng2 >>> (True, False, False) rng2.index(4) (55, 6) sшn(rngl), Как отмечалось ранее, у диапазона можно извлечь срез - результатом станет новый диапазон: >>> rngl[2:5] range 6) (3, Если требуется выполнить цикл определенное количество раз, можно использо­ вать цикл перебора с диапазоном. Например, следующий цикл выполнится 10 раз: for i in range(ll): print (i) Объект диапазона поддерживает полезные атрибуты start (начальное значение), stop (конечное значение) и step (шаг): >>> rngl.start, rngl.stop, rngl.step ( 1, 11, 1) Диапазон ♦ - это объект класса range; enшnera te (<последовательность> [, start=0] ) - выдает последовательность кор­ тежей из двух элементов: • суммы индекса очередного элемента заданной последовательности и значения параметра • start; значения очередного элемента последовательности. Примеры: >>> for 1 in enшnerate(languages): print(l[0], 1(1], end=' О Python 1 JavaScript ') 2 Java 3 Ruby 4 С++
Урок 5. Списки, кортежи, генераторы и словари 95 >>> for l_nurn, l_name in enurnerate(languages, start=l): print (l_nurn, l_name, end=' ') 1 Python 5.4.2. 2 JavaScript 4 Ruby 5 С++ Генераторы-выражения Генератор-выражение - 11 3 Java аналог спискового включения (см.разд. 5.1. 4). В отличие от спискового включения, генератор-выражение заключается в круглые скобки: ( <выражение> for <переменная> in <исходная последоват.> [if <условие>] ) Пример: >>> nurnЬers = (32, 6, 81, 9, 13] >>> squares = (n ** 2 for n in nurnЬers) >>> list(squares) (1024, 36, 6561, 81, 169] >>> squares = (n ** 2 for n in nurnЬers if n % 2 >>> tuple(squares) (6561, 81, 169) 1) Чтобы вывести содержимое генератора на экран, не забываем преобразовывать его в список или кортеж (почему- будет сказано в разд. 5.5. 5.9). Словари Словарь - это набор элементов произвольного типа, каждый из которых связан с определенным ключом (уникальной пометкой, выбираемой произвольно). Обра­ щение к элементам словаря производится по ключам. Словарь - это объект класса 5.5.1. Создание словарей dict. Словарь можно создать двумя способами: ♦ указав пары формата <ключ>: <значение> через запятую и заключив их в фигур­ ные скобки: >>> client = {'system': 'Windows', 'platform': 'React'} Записав фигурные скобки вплотную - {), - мы создадим пустой (без элемен­ тов) словарь; ♦ создав объект класса dict, записав выражение одного из следующих форматов: diсt(<ключ ]>=<значение 1>, <ключ 2>=<значение <ключ N>=<значение 2>, N>)
Часть 96 dict (<список или кортеж из diсt(<словарь /. Язык Python: основные инструменты списков или кортежей>) (или иное отображение 1 )>) Примеры: >>>#Первый формат >>> server = dict(systern='Linux', platfoпn='Python') >>> server {'systern': 'Linux', 'platfoпn': 'Python'} >>>#Второй формат >>> proxy = dict([ ('systern', 'Linux'), ('platfoпn', 'nginx')]) >>> proxy { 'systern': 'Linux', 'platfoпn': 'nginx' 1 Третий формат создает копию заданного словаря (или иного отображения). Как говорилось ранее, элементы словаря могут относиться к любому типу: >>> dct = {'first': 123, 'second': (5, 6.7], 'third': 5.5.2. 'bcd'}} Обработка словарей 5.5.2.1. ♦ {'а': Простейшие операции над словарями записав в квадратных скобках его ключ: Извлечь элемент словаря - >>> server['systern'] client['platfoпn'], ( 'React ' , ' Linux' ) Попытка обратиться по несуществующему ключу приведет к ошибке кeyError: >>> proxy [ 'version'] KeyError: 'version' Чтобы обратиться к элементу словаря, вложенного в другой словарь, следует указать две пары квадратных скобок и записать в первой паре ключ самого вло­ женного словаря во «внешнем» словаре, а во второй паре - ключ нужного эле­ мента во вложенном словаре: >>> dct [ 'third'] 'bcd' ♦ ('а'] Проверить существование ключа в словаре in или невхождения - использовав оператор вхождения not in: >>> 'version' in proxy, 'version' not in proxy (False, True) ♦ Изменить значение элемента словаря - присвоив ему новое значение: >>> server['systern'] = 'Windows' >>> server {'systern': 'Windows', 'platfoпn': 'Python'} 1 Об отображениях рассказано в разд. 5. б.
Урок 5. Списки, кортежи, генераторы и словари 97 Если указать элемент с несуществующим ключом, этот элемент будет создан: >>> proxy['version'] = '1.29' >>> proxy 'platfoпn': {'system': 'Linux', 'version': '1.29'} 'nginx', ♦ Узнать размер словаря (количество элементов в нем) - воспользовавшись функцией len () : >>> len(server), len(proxy) 3) (2, ♦ Перебрать элементь1 словаря применив цикл перебора. В этом случае в пере­ - менную, указанную в цикле, будет заноситься ключ очередного элемента сло­ варя: >>> for n in server: print(f'server[\'{n}\']: \'{server[n]}\'') server['system']: 'Windows' 'Python' server['platfoпn']: ♦ Объединить два словаря • двумя способами: применив оператор объединения 1 (вертикальная черта): {'е': >>>а= I а <<< 9, 'f': 10}; Ь = {'g': 11, 'h': 12} Ь {'е': • - 9, 'f': 10, 'g': 11, 'h': 12} или вот так: >>> {**а, **Ь} {'е': 9, 'f': 10, 'g': 11, 'h': 12} Если объединяемые словари содержат элементы с одинаковыми ключами, зна­ чения этих элементов будут взяты из второго словаря. Также поддерживается оператор комбинированного присваивания >>> al= 5.5.2.2. =: а Ь; {'е': 1 9, 'f': 10, 'g': 11, 'h': 12} Добавление, извлечение и удаление элементов словаря Для удаления элементов словаря можно использовать оператор del: >>> del a['g'] >>> а {'е': 9, 'f': 10, 'h': 12} Класс словаря dict поддерживает следующие полезные методы: ♦ get ( <ключ> [, <значение по умолчанию>=Nоnе] ) - возвращает значение элемента с заданным ключом. Если такого ключа в текущем словаре нет, возвращается ука­ занное значение по умолчанию:
Часть 98 /. Язык Python: >>> server. get ( 'platform') , server. get ( 'version') , server. get ( 'version', ( ' Python' , None, ' О . О ' ) ♦ setdefaul t (<ключ> [, <значение по основные инструменты 'О.О') умолчанию>=Nоnе] ) - возвращает значение элемента с заданным ключом. Если такого ключа в текущем словаре нет, создает новый элемент с заданным значением по умолчанию и возвращает это значение: >>> server.setdefault('platform'), server.setdefault('version', '1.0') ( ' Python' , ' 1 . О ' ) >>> server ('system': 'Windows', 'platform': 'Python', 'version': '1.0'} ♦ рор (<ключ> [ , <значение по умолчанию>] ) - удаляет из текущего словаря элемент с заданным ключом и возвращает его значение. Если такого ключа нет, возвращает указанное значение по умолчанию, а если оно не указано, выдает ошибку типа KeyError: >>> server.pop('version'), server.pop('environment', 'simple') ( '1. О', 'simple') >>> server ('system': 'Windows', 'platform': 'Python'} ♦ popi tem () - удаляет из текущего словаря последний элемент и возвращает кор­ теж из двух элементов: ключа и значения удаленного элемента словаря. Если словарь пуст, выдает ошибку типа KeyError: >>> proxy.popitem() ('version', '1.29') ♦ clear () - 5.5.2.3. удаляет из текущего словаря все элементы. Результата не возвращает. Перебор элементов словаря Класс dict поддерживает три генератора-метода, которые можно использовать для перебора элементов словаря: ♦ i tems () - выдает последовательность кортежей из двух элементов: ключа и значения очередного элемента текущего словаря: >>> for k, v in client.items(): print(f'(k}: (v}') system: Windows platform: React ♦ ♦ values () keys () - выдает последовательность из значений элементов текущего словаря; выдает последовательность ключей элементов текущего словаря. Ана­ логичного эффекта можно достичь, просто указав в цикле перебора сам словарь (см. разд. 5.5.2.1). Чтобы попрактиковаться, напишем программу (листинг 5.3), которая принимает от пользователя строку и проводит частотный анализ присутствующих в ней символов
Урок 5. 99 Списки, кортежи, генераторы и сповари (вычисляет, сколько вхождений того или иного символа присутствует в этой строке). print ('Частотный анализ строк') source_string = inрut('Введите анализируемую строку: ') if source_string: source_string = source string.strip() .lower() result = {) for char in source_string: n = result.get(char, О) result[char] = n + 1 for k, v in result.iterns(): print(f'\'{k)\': {v:d)', end=', ') завершения программы нажмите 1 # 2 3 # # 4 # 5 6 # 7 # 8 # 9 # print () input('Для # <Enter>. ') Отметим наиболее примечательные выражения этого кода. Сначала проверяем, ввел ли пользователь какую-либо строку (поз. 1в листинге 5.3). Мы пользуемся тем, что любая непустая строка, указанная в качестве условия в вы­ ражении ветвления, преобразуется в тrue, а пустая - в False (см. разд. 3. 7). Далее обрезаем пробельные символы в начале и в конце введенной строки и преоб­ разуем все ее символы к нижнему регистру (поз. 2). Для этого записываем вызовы методов strip () и lower () своего рода цепочкой. Тогда метод strip () будет вызван у изначальной строки, а метод lower () у результата, возвращенного методом strip (). В Python-кoдe такие цепочки методов встречаются очень часто. Создаем и присваиваем переменной result пустой словарь, в котором будет хра­ ниться результат работы программы (поз. 3). Ключи элементов этого словаря совпадут с символами строки, а значения будут представлять собой величины ко­ личества вхождений этих символов. Перебираем исходную строку в цикле (поз. запрашиваем у словаря resul t 4). Получив очередной символ строки, значение элемента, ключ которого совпадает с этим символом, т. е. предыдущее количество вхождений этого символа (поз. 5). применяем метод get () словаря, указав у него вторым параметром число О, при запросе из словаря несуществующего символа мы получим Для чего - тогда количество его вхождений, равное нулю. А потом увеличиваем извлеченное количество вхождений символа на 1 и снова присваиваем тому же элементу словаря (поз. Затем перебираем словарь resul t (поз. (поз. 8). 7) 6). и выводим его содержимое в консоли В вызове функции print () в параметре end указываем запятую с пробелом, чтобы символы и количества из вхождения выводились в строку через запятую для компактности. -
Часть 100 Язык /. Python: основные инструменты Если теперь вывести предупреждение о необходимости нажатия клавиши <Enter> для завершения программы, оно отобразится в той же строке, что и выведенный программой результат, что будет некрасиво. Поэтому предварительно выводим пусrую строку (поз. 5.5.3. 9). Сравнение словарей Словари можно сравнивать, применяя операторы сравнения == и ! =. Словари счи­ таются равными, если содержат одинаковый набор элементов с одинаковыми клю­ чами и значениями, однако порядок элементов в них может быть разным: >>> {'а': 1, 'Ь': 2} {'а': 1, 'Ь': 2} 1, 'Ь': 2} {'а': 1, 'Ь': 3} 1, 'Ь': 2} {'а': 1, 'Ь': 2, 1, 'Ь': 2} { 'Ь': 2, 'а': 1} True >>> {'а': False >>> {'а': 'с': 3) False >>> {'а': True 5.5.4. 11 Словарные включения Словарн~е включе~ие - выражение, создающее новый словарь на основе элементов 1заданнои исходнои последовательности: <выражение ключа>: <выражение значения> for ~ (<переменная ключа>, <переменная значения>) <исходная последовательность> [if in ~ <условие>]) Задаваемая исходная последовательность должна содержать вложенные последова­ тельности из двух элементов. На основе первого элемента будет вычислен ключ, а на основе второго - значение элемента формируемого словаря. Словарное включение перебирает исходную последовательность, присваивая первый элемент очередной вложенной последовательности заданной переменной ключа, а второй элемент - переменной значения. Выражение ключа производит заданные вы­ числения над ключом, присвоенным переменной ключа, а выражение значения- над значением, сохраненным в переменной значения. Элемент с вычисленными ключом и значением добавляется в формируемый словарь, который потом будет возвращен в виде результата. Если указано условие, обрабатываться будут лишь те элементы исходной последова тельности, в отношении которых это условие станет истинным. Пример словарного включения, которое на основе исходной последовательности, содержащей пары «будущий ключ - число», создает словарь чисел, при этом добавляя к ключу каждого элемента цифру - с кубами этих 3: >>> nшnЬers = (('а', 5), ('Ь', 90), ('с', 4.2), ('d', -81)) >>> {k + '3': v ** 3 for (k, v) in nшnЬers} {'а3': 125, 'Ь3': 729000, 'с3': 74.08800000000001, 'd3': -531441}
Урок 5. Списки, кортежи, генераторы и словари 101 Пример аналогичного словарного включения, которое обрабатывает лишь отрица­ тельные числа: >>> {k + '3': v ** 3 for (k, v) in nшnЬers if v < О} {'d3': -531441} 5.5.5. Словари со значением по умолчанию defaultdict Можно создать словарь, который при обращении к элемеmу с несуществующим ключом будет возвращать «пустое» значение по умолчанию заданного типа: О, пус­ тую строку, пустой список и т. п. Ошибка типа KeyError при этом не возникнет. значением со Словарь defaul tdict () ИЗ модуля по умолчанию создается и возвращается функцией collections: defaultdict(<тип значения по умолчанию>[, <ключ ]>=<значение 1>, <ключ 2>=<значение <ключ N>=<значение 2>, . N>] ) Такой словарь полностью аналогичен обычному, только имеет тип defaultdict. Пример: >>> frorn collections import defaultdict >>>#Указываем строковый - str - тип значения по умолчанию >>> proxy = defaultdict(str, systern='Linux', platforrn='nginx') >>>#Созданный словарь имеет тип defaultdict >>> proxy defaultdict(<class 'str'>, {'systern': 'Linux', 'platforrn': 'nginx'}) >>>#Обращаемся к существующему элементу >>> proxy['platforrn'] 'nginx' >>>#Обращаемся к несуществующему элементу и получаем значение по >>>#умолчанию заданного типа - пустую строку >>> proxy [ 'version'] 1 1. 29' >>> proxy [ 'version'] proxy >>> defaultdict(<class 'str'>, {'systern': 'Linux', 'platforrn': 'nginx', 'version': '1. 29'}) 5.6. Отображения Словарь - это отображение. Отобра:жение - значение, представляющее собой набор элементов, каждый из кото­ рых связан с уникальным ключом. Можно сказать, что каждый ключ отображается на соответствующий ему элемент.
Часть 102 5. 7. /. Язык Python: основные инструменты Особенности хранения значений в переменных. Ссылки Проведем следующий эксперимент: >>>#Создаем список >>> languages = и присваиваем его переменной ['Python', 'JavaScript', >>>#Присваиваем содержимое 'RuЬy', 'Java'] этой переменной другой переменной >>> languages2 = languages >>>#Изменяем значение элемента списка из первой переменной >>> languages[l] = 'Perl' >>>#Удостоверимся, что >>> languages ['Python', 'Perl', >>>#Посмотрим на значение элемента изменилось 'Ruby', 'Java'] список из второй переменной ... >>> languages2 ['Python', 'Perl', 'Ruby', 'Java'] Выходит, список из второй переменной тоже изменился? Но почему? Значения всех без исключения типов (списков, кортежей, словарей, строк, чисел и др.) в Python хранятся не в самих переменных, а отдельно от них. В переменных хранятся лишь ссылки на эти значения. Созданный нами список был сохранен в отдельной области памяти, а первой пере­ менной была присвоена ссылка на него. После присваивания другой переменной в нее была занесена копия ссылки на тот же список. Проверить, ссылаются ли две переменные на одно и то же значение, можно с по­ мощью следующих операторов: ♦ <переменная 1> is <переменная 2> оператор идентичности возвращает True, если обе переменные хранят ссылку на одно и то же значение, и False в противном случае: ♦ >>> lstl = [1, 2, 3]; lst2 = lstl; lst3 >>> lstl is lst2, lstl is lst3 (True, False) [3, 2, 1] <переменная 1> оператор неидентичности is not <переменная 2> - - воз­ вращает True, если обе переменные ссылаются на разные значения, и False в противном случае: >>> lstl is not lst2, lstl is not lst3 (False, True) Если же необходимо сохранить в двух разных переменных два экземпляра одного и того же значения, следует создать и присвоить их по отдельности: >>> languages = ['Python', 'JavaScript', 'RuЬy', 'Java'] >>> languages2 = ['Python', 'JavaScript', 'Ruby', 'Java'] >>> languages[l] = 'Perl'
Урок 5. Списки, кортежи, генераторы и сповари 103 >>> languages ['Python', 'Perl', 'Ruby', 'Java'] >>> languages2 ['Python', 'JavaScript', 'Ruby', 'Java'] Но проще всего создать один экземпляр и потом получить его копию. 5.8. Копирование списков, кортежей и словарей Получить копию какого-либо списка, кортежа или словаря можно двумя способами: ♦ создать на его основе другой объект того же класса: >>> languages = ['Python', 'JavaScript', 'RuЬy', 'Java'] >>>#Создаем второй список - копию первого >>> languages2 = list(languages) >>>#Изменяем элемент >>> languages2[1] >>>#Изменился = списка второго 'Perl' второй список >>> languages2 ['Python', 'Perl', 'Ruby', 'Java'] >>>#Первый остался неизменным >>> languages ['Python', 'JavaScript', 'Ruby', 'Java'] Однако если копируемый список или словарь содержит в себе другие списки или словари, то будут скопированы лишь ссылки на них, и мы получим поверхностную копию: lstl = [ 1, 2, 3, {'а': 5, lst2 = list(lstl) lst2[-l] ['а'] = 5000 lst2 [1, 2, 3, {'а': 5000, 'Ь': >>> lstl [1, 2, 3, {'а': 5000, 'Ь': >>> >>> >>> >>> 'Ь': 6)] 6)] 6)] Видно, что четвертые элементы обоих списков ссылаются на один и тот же экземпляр вложенного словаря; ♦ воспользоваться функцией deepcopy (<значение>) из модуля сору и все вложенные значения, создавая и возвращая глубокую копию: from сору import deepcopy lst2 = deepcopy(lstl) lst2[-l] ['а'] = 5000 lst2 [1, 2, 3, {'а': 5000, 'Ь': 6)] >>> lstl [1, 2, 3, {'а': 5, 'Ь': 6)] >>> >>> >>> >>> она копирует
Часть 104 /. Язык Python: основные инструменты Получить копию списка или кортежа также можно, взяв его срез с первого по по­ следний элемент: >>> languages2 5.9. = languages[:] Что еще нужно знать о генераторах и не только ♦ Генератор функция или выражение - - сам по себе возвращает не готовую последовательность, а особый объект, называемый итератором, или генера­ торным объектом. Он при каждом обращении вычисляет и выдает очередной элемент генерируемой последовательности, а когда последовательность закон­ чится, уведомляет об этом исполняющую среду. Итератор, как и любое другое значение, можно присвоить какой-либо перемен­ ной для дальнейшего использования. А также вывести в интерактивной обо­ лочке: >>> nшnЬers = (32, 6, 81, 9, 13) >>>#Генератор-выражение возвращает итератор >>>#с базовой функциональностью >>> (n ** 2 for n in nшnЬers) <generator object <genexpr> at Ox0000029965F32400> >>>#А генератор-функция >>>#объект класса range() возвращает более сложный итератор - range >>> range(ll) range(O, 11) ♦ Функция rеvеrsеd(<последовательность>) возвращает итератор, при каждом об­ ращении выдающий элементы заданной последовательности, начиная с конца: >>> list(reversed([l, 2, 3))) (3, 2, 1] ♦ Множество Python - непронумерованная изменяемая последовательность, хра­ нящая лишь уникальные значения. Является объектом класса set. >>>#Создаем объект множества на основе заданного кортежа >>> s 1 = set ( ( 1, 4, 7, 2, 1, 9, 4) ) >>>#Повторяющиеся элементы отбрасываются >>> sl {1, 2, 4, 7, 9) >>>#Добавляем во множество пару элементов »> sl.add(S) >>>#Повторяющийся элемент добавлен не будет >>> sl.add(9) >>> sl {1, 2, 4, 5, 7, 9} >>>#Удаляем элемент по его индексу >>> sl.discard(l)
Урок 5. Списки, кортежи, генераторы и словари 105 >>> sl {2, 4, 5, 7, 9) >>>#Создаем еще одно множество другим способом >>>#элементы через запятую в фигурных - указав его скобках >>> s2 = {4, 9, 14, 22) >>>#Проверяем вхождение, узнаем размер множества >>> 9 in s2, 56 in s2, len(s2) (True, False, 4) >>>#Объединяем множества >>> sl I s2 {2, 4, 5, 7, 9, 14, 22} >>>#Вычисляем разницу множеств >>> sl - s2 {2, 5, 7} Включение во множ·ество - выражение, создающее множество на основе элемен­ тов заданной исходной последовательности и аналогичное списковому включению (см.разд. 5.1.4): >>> {n ** 2 for n in numЬers} {1024, 6561, 36, 169, 81} >>> {n ** 2 for n iп numЬers if n % 2 {81, 6561, 169} 1} Множества находят ограниченное применение. Чаще всего их используют, что­ бы очистить последовательность от повторяющихся элементов: >>>#Исходная последовательность кортеж - >>> source = 1, 4, 7, 2, 1, 9, 4 >>>#Преобразуем его во множество - повторяющиеся элементы удаляются >>> s = set(source) >>>#Преобразуем множество в кортеж >>> destination = tuple(s) >>> destination (1, 2, 4, 7, 9) ♦ Неизменяемое множество - объект класса frozenset: >>> frozenset( (1, 4, 7, 2, 1, 9, 4)) frozenset({l, 2, 4, 7, 9)) 5.1 О. 1. Самостоятельные упражнения Перепишите программу из листинга 4.1, выводящую синусы углов, с использо­ ванием диапазона. 2. Напишите с использованием кортежа чисел и функции sample(} (см.разд. 5.1.2.5) программу, которая при каждом запуске выдает три псевдослучайных числа от до 3. 1 6. Перепишите программу частотного анализа строки из листинга 5.3, использовав словарь со значением по умолчанию. Попробуйте для вывода результата приме­ нить списковое включение, подставив в него вызов функции print ().
Урок6 Функции Функция - языковая конструкция, выполняющая какое-либо достаточно сложное действие. Может принимать произвольное количество исходных значений (пара­ метров) и возвращать результат. Вызов функции - это обращение к ней, возможно, с передачей требуемых пара­ метров. 6.1. Вызов функций Вызов функции записывается в следующем формате: <имя функции> ( [ <параметр 1>, <параметр 2>, . .. ' <параметр N> ] ) Каждый из параметров можно указать в качестве: ♦ позиционного (идентифицируемого по его позиции) - просто записав его зна­ чение в требуемой позиции; ♦ именованного (идентифицируемого по его имени)- записав конструкцию фор­ мата: <имя параметра>=<значение параметра> Пример вызова функции print () с передачей ей двух позиционных и одного име­ нованного параметров: print(a, Ь, sep=', ') Если функция вызывается без передачи ей параметров, после её имени ставятся пус­ тые круглые скобки. Вот пример вызова функции randorn () из модуля randorn без параметров: frorn randorn irnport randorn randorn nwnЬer = randorn () 6.1.1. Виды параметров функций Параметры у функций бывают трех видов: ♦ строго позиционные - указываются только как позиционные. Так, строго позиционными являются оба параметра встроенной функции (см.разд. 4.1. 3.1); round ()
Урок б. Функции ♦ 107 строго именованными - указываются только как именованные. К строго именованным относятся, в частности, параметры sep и end функции print () (см.разд. 2.2.2); ♦ двоякими Двояким - могут указываться в качестве как позиционных, так и именованных. например, является, enшnerate () (см.разд. 5.4.1) - второй генератора-функции параметр с именем start. Так, следующие два выражения полностью идентичны: seq = seq = enшnerate(languages, enшnerate(languages, start=l) 1) В каком виде записывать двоякие параметры - дело вкуса. Вызов функции с позиционными параметрами выглядит компактнее, а с именованными - на­ гляднее. Указание строго позиционного параметра в качестве именованного или строго именованного в качестве позиционного приведет к ошибке типа TypeError. Большинство функций принимают строго определенное количество параметров (часть которых может быть необязательной для указания). Но есть функции, спо­ собные принять произвольное количество параметров (например, функции min () и max() -см.разд. 4.1.3.1). 6.1.2. Распаковка последовательностей и отображений в вызовах функций Вызываемой функции можно передать в качестве параметров элементы какой-либо уже существующей последовательности и (или) отображения. Элементы последо­ вательности будут переданы в качестве позиционных параметров, а элементы ото­ бражения - именованных, причем ключи элементов станут именами параметров. Последовательность и отображение записываются в вызове функции в составе пе­ редаваемых ей параметров. Последовательность предваряется символом звездочки ( *), отображение - двумя звездочками ( * *). Для примера вызовем функцию print ( J, передав ей четыре параметра из последо­ вательности (у нас - кортежа) values и два параметра из отображения (словаря) options: >>> values = ('NiceGUI', 'Sanic', 'FastAPI', 'Django') >>> options = {'sep': ', ', 'end': '') >>> print(*values, **options) NiceGUI, Sanic, FastAPI, Django Эта возможность особенно полезна, если требуется вызвать одну и ту же функцию несколько раз с одинаковыми значениями у части параметров не набирать их заново: print('Toga', 'PyQt', 'Kivy', 'Tkinter', **options) - чтобы каждый раз
Часть 108 6.2. /. Язык Python: основные инструменты Объявление своих функций· Разработчик программы может создать произвольное количество своих функций и вызывать их в дальнейшем так же, как и функции, встроенные и реализованные в стандартной библиотеке. 6.2.1. Объявление функции Объявление функции 11 выражение, описывающее функцию, создаваемую програм­ мистом. Записывается в формате: def <имя функции> ( [ <имя параметра 1>, ... , <имя параметра N> ] ): <тело функции> Тело функции - код, собственно реализующий функциональность функции. Оформ­ ляется в виде блока. Имя функции должно быть уникальным в пределах модуля, в котором присутствует объявление, а имена параметров - в пределах самой этой функции. Все имена долж­ ны удовлетворять тем же требованиям, что и имена переменных (см.разд. Локш,ьная переменная 11 1.4). переменная, созданная в теле функции и существующая только там. После завершения выполнения функции уничтожается. При вызове функции создаются локальные переменные, одноименные параметрам и хранящие их значения. Из этих переменных в теле функции можно получить зна­ чения параметров, переданные при вызове. Пример объявления функции di vision (), принимающей два числа и вычисляющей и выводящей частное от их деления: >>> def division(a, Ь): result =а/ Ь print(f'{a} / {Ь} {result} ') В теле этой функции создается локальная переменная resul t - для временного хранения получившегося частного. Проверим нашу первую функцию в деле: >>> division(72, 8) 72 / 8 = 9. О >>> division(l024, 16) 1024 / 16 = 64.0 >>> division(2.3, 78.34) 2.3 / 78.34 = О.029359203472044928 Тело этой функции будет выполняться от первого выражения до самого последне­ го, после чего завершится. Результата эта функция не возвращает. Чтобы вернуть из функции заданный результат, в нужное место ее тела следует вставить оператор возврата return return: [<возвращаемый результат>=Nоnе]
Урок 6. 109 Функции 1По достижении оператора возврата выполнение функции завершится. Объявим более полезную функцию avg (), принимающую последовательность чи­ сел и возвращающую их среднее арифметическое: >>> def avg(nшnЬers): result = sum(nшnЬers) / return resul t len(nшnЬers) Проверим, как она работает: >>> avg([l, 2, 3]) 2.0 >>> avg([800, 8000, 80000]) 29600.0 >>> avg([2.4, 6.7, -96.43, 858.904]) 192.8935 Указав в качестве тела функции пустой оператор pass, мы объявим пустую функ­ цию, которая ничего не делает: >>> def empty_func(): pass Обычно пустые функции используются в качестве временных «заглушек», которые в дальнейшем будут наделены полезной функциональностью. Доступ к глобальным переменным из тела функции 6.2.2. 1Глобальная переменная 11 любая переменная, созданная в обычном коде, вне объяв- лений функций. Обращаться к глобальным переменным из тела функции: ♦ только для считывания их значений - можно свободно: >>>#Создаем глобальную переменную >>> var = 123 >>> def func(): # В теле функции print (var) выводим значение глобальной переменной >>> func () 123 >>> var 123 ♦ для присваивания им новых значений: • если присваивание выполняется перед считыванием - будет создана одно­ именная локальная переменная, и последующие обращения будут выпол-
Часть 110 /. Язык Python: основные инструменты няться именно к этой локальной переменной. Глобальная переменная ока­ жется недоступной: >>> def func2(): var = 321 # Будет выполнено обращение # локальной переменной print (var) к только что созданной >» func2 () 321 >>> var 123 • если присваивание выполняется после считывания - попытка считать значе­ ние приведет к ошибке типа UnbouпdLocalError: >>> def func3(): print (var) var = 321 >>> func3 () UnboundLocalError: cannot access local it is not associated with а value variaЫe 'var' where Чтобы из тела функции без проблем обращаться к глобальным переменным и для считывания, и для присваивания значений, следует явно пометить эти переменные как глобальные. Это выполняется посредством оператора global <переменная 1>, <переменная 2>, . . . , global: <переменная N> Пример: >>> def func4(): # Помечаем global var print (var) var = 321 переменную var как глобальную >>> func4 () 123 >>>#Присваивание нового значения >>>#в теле выполнено успещно функции быпо глобальной переменной var >>> var 321 6.2.3. Необязательные параметры Необязательный параметр при вызове функции можно не указывать - тогда он получит установленное у него значение по умолчанию. Функция может содержать произвольное количество необязательных параметров.
111 Урок б. Функции Необязательный параметр в выражении объявления функции (см.разд. 6.2.1) обо­ значается следующим образом: <имя параметра>=<значение по умолчанию> Пример: >>> def division2(a, Ь=2): print(f'(a} / (Ь} =(а/ Ь}') >>> division2(12, 2) 12 / 2 = 6.0 >>> division2(23, 56) 23 / 56 = О.4107142857142857 >>> division2(12) 12 / 2 = 6.0 Все необязательные параметры в выражении объявления функции должны распола­ гаться в конце перечня, приведенного в круглых скобках: >>> def func(a, Ь, c=l, d=2, print(a, Ь, с, d, е) >>> func(l0, 10 11 12 >>> func(l0, 10 11 12 е=3): 11, 12, 13, 14) 13 14 11, 12) 2 3 Если требуется указать значение не первого из необязательных параметров, необ­ ходимо задать значения у всех предшествующих ему необязательных параметров, хотя бы совпадающие с их значениями по умолчанию. Вот пример вызова функции с указанием значения у второго необязательного параметра: >>> func(l0, 11, 2, 234) 10 11 2 234 3 Пропуск параметра при вызове функции приведет к ошибке типа SyntaxError: >>> func(l0, 11, , 234) SyntaxError: invalid syntax Второй и последующие необязательные параметры при вызове функции удобно указывать в виде именованных: >>> func(l0, 11, d=234) 10 11 1 234 3 6.2.4. Произвольное количество параметров Можно объявить функцию, принимающую произвольное количество параметров - позиционных или (и) именованных. Для этого в перечень параметров в выражении объявления функции (см.разд. метров (или сразу оба): 6.2.1) следует вставить один из следующих пара­
Часть 112 ♦ *<имя параметра> - /. Язык Python: основные инструменты получит в качестве значения кортеж со значениями всех по­ зиционных параметров, записанных в этом месте при вызове функции. Должен находиться после всех явно заданных позиционных параметров. Традиционно этому параметру дают имя args; ♦ **<имя параметра> ниями всех - получит в качестве значения словарь с именами и значе­ именованных параметров, записанных в этом месте при вызове функции. Должен находиться после всех явно заданных именованных пара­ метров. По общепринятой практике, этому параметру дают имя kwargs. В качестве примера объявим простую функцию func (}, принимающую два обяза­ тельных (а и ь) и произвольное количество прочих позиционных параметров, один обязательный (с) и произвольное количество прочих именованных параметров: >>> def func(a, Ь, *args, print(a, Ь, args, с, **kwargs}: kwargs} с, >>> func(l, 2, 3, 4, 5, с=6, d=7, е=8, f=9} 1 2 (3, 4, 5) 6 {'d': 7, 'е': 8, 'f': 91 >>> func(l, 2, с=6, d=7) 1 2 () 6 { 'd': 7} >>> func(l, Ь=2, с=6, d=7) 1 2 () 6 {'d': 71 >>> func(a=l, Ь=2, с=6, d=7} 1 2 (} 6 {'d': 71 Объявим более простую и более полезную функцию avg2 (), принимающую произ­ вольное количество чисел и возвращающую их среднее арифметическое: >>> def avg2(*nшnЬers): return sum(nшnЬers) / len(nшnЬers) >>> avg2(1, 2, 3) 2.0 >>> avg2(4, 67, -29.5, 67.34, 34.93666666666667 6.2.5. .78, 100) Строго позиционные, строго именованные и двоякие параметры При объявлении функции (см.разд. относиться те или иные параметры - 6.2.1) мы можем указать, к какому виду будут к строго позиционным, строго именованным или двояким. Для этого достаточно записать их в нужных местах списка парамет­ ров и вставить нужные разделители согласно табл. 6.1. Для примера объявим функцию func2 (), аналогичную функции func () из разд. у которой параметр а сделаем строго позиционным, а параметр ь-двояким: 6.2.4,
113 Урок б. Функции Таблица 6.1. Соответствие местоположения параметров в объявлении функции их видам Вид параметров Параметры и разделители def func( ь, а, Строго позиционные с, Разделитель / d, * е, 1 f, - прямой слеш - звездочка или кортеж строго Двоякие *args Разделитель ПОЗИЦИОННЫХ параметров g, h, Строго именованные параметры **kwargs Словарь строго именованных параметров ) >>> def func2(a, /, Ь, *args, с, **kwargs): print(a, Ь, args, с, kwargs) 4, 5, с=б, d=7, е=В, f=9) 1 2 (3, 4, 5) 6 {'d': 7, 'е': 8, 'f': 9} >>> func2(1, 2, >>> func2(1, З, Ь=2, с=б, d=7) 1 2 () 6 {'d': 7} >>> # Попьrrка указать строго позиционный параметр в качестве именованного >>>#приведет к ошибке типа >>> func2(a=l, Ь=2, с=б, TypeError d=7) TypeError: func2() missing 1 required positional argument: 'а' Пример функции, принимающей обязательный строго позиционный, необязатель­ ный двоякий и необязательный строго именованный параметры: >>> def funcЗ(a, /, Ь=lО, print(a, Ь, с) *, c=l00): funcЗ(l, 1000, c=l000000) 1 1000 1000000 >>> funcЗ(l, Ь=lООО, c=l000000) 1 1000 1000000 >>> funcЗ(l, 1000) 1 1000 100 >>> funcЗ(l) 1 10 100 >>>
Часть 114 6.3. /. Язык Python: основные инструменты Функция как значение Любая функция сама по себе является значением - объектом типа function: >>> def func(a): return а* 2 >>> type(func) <class 'function'> Любую функцию можно присвоить какой-либо переменной и использовать послед­ нюю для вызова этой функции: >>> some func = func >>> some_func(lб) 32 Функцию можно передать в одном из параметров другой функции, которая вызовет ее в своем теле, получит результат и использует для дальнейших вычислений: >>> def func2(fn, return fn а, (а) Ы: / Ь >>> func2(func, 16, 8) 4.0 >>> func2(some func, 128, 1024) 0.25 6.3.1. Разные функции и методы, принимающие с параметрами другие функции Рассмотрим несколько полезных функций и методов разных классов, которые при­ нимают в качестве параметров другие функции: ♦ min (), встроенная функция, min (<значение 1>, . . ., да-да, наша хорошая знакомая: - <значение min(<последовательность>[, 2>, ... , <знач. N> [, key=None]) key=None]) Если указанные значения не являются числами или заданная последовательность содержит не числа, то в параметре key указывается функция, принимающая с единственным параметром очередное значение или элемент последовательности и возвращающая его преобразованным в целочисленный или вещественный тип. Пример: >>> def fnl(el): return el[l] >>> min(('a', 5), ( 'd', -78) ('Ь', -3), ('с', 234), ('d', -78), key=fnl)
Урок б. Функции 115 ♦ max ( ) , встроенная функция, аналогично min ( ) : - >>> def fn2(el): return el [ 'height'] >>> max({ 'point': 1, 'height': 3289.8), {'point': 2, 'height': 2126.3), key=fn2) {'point': 1, 'height': 3289.8) ♦ sorted (), встроенная функция, - возвращает отсортированную копию заданной последовательности: sоrtеd(<последовательность>[, key=None] [, reverse=False]) Пример: >>> sorted( (4, 2, 9, -45, 100, 7]) (-45, 2, 4, 7, 9, 100] Однако если заданная последовательность содержит нечисловые элементы, воз­ можны эксцессы. Например, в следующем примере строки на русском языке выстроены в неправильном порядке: >>> sorted ( ( 'Язык 2', 'язык 1', 'программа') ) ['Язык 2', 'программа', 'язык 1'] В этом случае в параметре key следует задать преобразующую функцию, кото­ рая примет в параметре исходный элемент последовательности и вернет его в преобразованном нужным образом виде: »> def fn3 (el): return el.lower() >>> sоrtеd(('Язык 2', ['программа', 'язык 'язык 1', 1', 'программа'), 'Язык key=fn3) 2'] Значение True параметра reverse сортирует последовательность в обратном по­ рядке: >>> sorted([4, 2, 9, -45, 100, 7], reverse=True) (100, 9, 7, 4, 2, -45) ♦ sort ( [ key=None J [, ] [ reverse=False] ) , метод класса list, - то же самое, что и функция sorted () , только сортирует текущий список и не возвращает резуль­ тата; ♦ filter (<функция>, <последовательность>), встроенный генератор-функция, - перебирает элементы указанной последовательности, передает очередной эле­ мент заданной функции и, если последняя вернет True, заносит этот элемент в ге­ нерируемую последовательность: >>> def fn4(el): return el < О
Часть 116 >>> list{filter{fn4, [-78, -97.6] ♦ Язык /. Python: основные инструменты (4, 29, -78, 0.32, -97.6))) reduce {), функция из модуля functools, - передает заданной функции один за другим элементы указанной последовательности: rеduсе{<функция>, <последовательность>[, <начальное значение>]) В функцию при каждом вызове передаются два параметра: предыдущий возвра­ щенный ею результат и значение очередного элемента последовательности. При первом вызове функции ей передаются: • если задано начальное значение - • если начальное значение не задано оно и первый элемент последовательности, - первый и второй элементы последова- тельности. Функция reduce {) возвращает последний результат, выданный указанной функ­ цией. Получим сумму квадратов чисел из заданного кортежа: >>> def fn5{prev, el): return prev + el ** 2 >>> frorn functools irnport reduce >>> reduce{fn5, (9, 45, 2, 84, 11), 9287 6.4. 11 О) Лямбда-функции Лямбда-функция - функция с упрощенным синтаксисом объявления. Не имеет имени 1. Выражение, объявляющее лямбда-функцию, записывается в формате: larnЬda [ <имя параметра 1>, <имя параметра 2>, <выражение, . . ., <имя парам. N>] : ~ аычислякщее возвращаемый результат> Выражение возвращает готовый объект функции, относящийся к типу function. Его следует присвоить какой-либо переменной или передать с параметром другой функции. Рассмотрим пример из разд. 6. 3.1, записанный с применением лямбда-функции: >>> fn6 = larnЬda el: el < О >>> list{filter{fn6, (4, 29, -78, 0.32, -97.6))) [-78, -97.6] Лямбда-функции могут иметь необязательные параметры. Применение лямбда-функций позволяет несколько сократить код (что и продемон­ стрировано на приведенном примере). Однако реализовать более или менее слож­ ную логику в таких функциях ДОВОЛЬНО сложно. 1 Вследствие чего такие функции иногда называют анонимными.
Урок 6. 6.5. 117 Функции Вложенные функции Вложенная функция 11 функция, объявленная в теле другой функции (родительской). Доступна исключительно в теле родительской функции. Пример простейшей вложенной функции: >>> def outer_func(): def inner_func(): print ('Вложенная функция') рrint('Родительская функция') inner_ func () >>> outer_func() Родительская Вложенная Вложенная функция функция функция может принимать произвольные параметры, возвращать какой-либо результат и создавать в своем теле локальные переменные. Также она может получать доступ к глобальным переменным, предварительно пометив их как глобальные оператором global (см. разд. 6.2.2): >>> var = 123 >>> def outer- func2(): def inner_func(): global var var = 321 inner- func () >>> var 123 >>> outer func2 () >>> var 321 Вложенная функция может обращаться к локальным переменным, созданным в те­ ле родительск.., 'f функции. Однако здесь возникнет проблема, аналогичная описан­ ной в разд. 6.2.2: значения локальных переменных из родительской функции могут быть прочитаны без проблем, однако при присваивании им новых значений в теле вложенной функции будут созданы одноименные им локальные переменные: >>> def outer_func3(): def inner_func(): var = 321 print ( 'Вложенная функция:', var) var = 123 print ('Родительская функция:', var) inner_ func () print ('Родительская функция:', var)
Часть 118 /. Язык Python: основные инструменты >>> outer func3() 123 Родительская функция: Вложенная функция: 321 123 Родительская функция: Решить эту проблему можно, явно пометив эти переменные как нелокальные по отношению к вложенной функции, применив оператор nonlocal, записываемый подобно оператору global: >>> def outer_func4(): def inner_func(): nonlocal var var = 321 print ( 'Вложенная функция:', var) var = 123 print ('Родительская функция: ', var) inner_ func () рrint('Родительская функция:', var) >>> outer func4() Родительская Родительская 6.5.1. 123 функция: Вложенная функция: 321 321 функция: Замыкания. Фабрики функций Зшwыкание - вложенная функция, сохраняющая в памяти («замыкающая в себе») значения локальных переменных из родительской функции (включая полученные ею параметры). Чтобы превратить вложенную функцию в замыкание, достаточно в ее теле обратиться к нужным локальным переменным из родительской функции и вернуть вложенную функцию из родительской в качестве результата. Для примера напишем функцию, которая создает и возвращает замыкания­ счетчики, при каждом вызове увеличивающие значение локальной переменной на единицу и возвращающие получившуюся сумму: >>> def counter_factory(): def counter(): nonlocal cnt cnt += 1 return cnt cnt = О return counter Функция counter _ factory () при вызове создаст локальную переменную cnt и вер­ нет вложенную функцию counter (). В теле вложенной функции выполняется обра­ щение к переменной cnt. Следовательно, эта вложенная функция станет замыка­ нием.
Урок б. Функции 119 В результате значение переменной cnt будет сохраняться в памяти между вызовами возвращенного замыкания, пока это замыкание существует. Удостоверимся сами: >>> counterl = counter_factory() >>> counterl () 1 >>> counterl () 2 >>> counterl () 3 Если же вызвать функцmо counter_ factory () еще раз, она вернет другой экземпляр замыкания, полностью независимый от предыдущего: >>> counter2 = counter_ factory () >>> counter2 () 1 >>> counter2 () 2 >>> counterl () 4 Функция counter_ factory () 11 Фабрика функций - это фабрика функций. функция, которая возвращает замыкания. Вот пример другой фабрики функций, выдающей замыкания, настроенные соглас­ но значению полученного параметра: >>> def counter_factory2(counter_numЬer): def counter(): nonlocal cnt cnt += 1 print (f' Счетчик cnt = О return counter >>> >>> >>> >>> >>> >>> >>> № ( counter_numЬer 1: = counter factory2(1) counterЗ () Счетчик № 1: 1 counterЗ () Счетчик № 1: 2 counterЗ () Счетчик № 1: 3 counter4 = counter factory2(2) counter4 () Счетчик № 2: 1 counter4 () Счетчик № 2: 2 counterЗ ( cnt) ')
Часть 120 6.6. /. Язык Python: основные инструменты Декораторы функций Декоршпор функции функция, дополняющая - или изменяющая работу другой функции, у которой указана (декорируемой функции). - Декоратор должен принимать единственный параметр функцию. Возвращать он должен ссылку на функцию - ссылку на декорируемую декорируемую или какую­ либо другую. Для указания декоратора у декорируемой функции применяется такой синтаксис: @<имя декоратора> <объявление декорируемой функции> Декоратор выполняется лишь один раз при объявлении функции. - Пример простейшего декоратора logger (), выводящего сообщение в консоли: >>>#Объявляем декоратор >>> def logger(fn): print ( 'Объявление return fn функции' ) >>>#Объявляем декорируемую функцию, >>>#Декоратор выведет сообщение: не забыв "Объявление указать у нее декоратор. функции". >>> @logger def fnl(): print ('Функция fnl () ') Объявление >>>#При функции вызове декорируем,ой функции декоратор уже не вызывается »> fnl () Функция fnl () Если требуется, чтобы декоратор срабатывал при каждом вызове декорируемой функции, необходимо объявить в декораторе вложенную функцию и вернуть ее в качестве результата (т. е. превратить декоратор в фабрику функций-декорато­ ров - см. разд. 6.5.1). Функция, возвращенная декоратором, и будет вызываться вместо декорируемой 11 функции. Напишем программу (листинг нять список числами, 6.1 ), которая с помощью оператора + будет напол­ находящимися в заданном диапазоне с указанным шагом, а также вычислять и выводить время, в течение которого список будет заполнен. Код, заполняющий список, реализуем в виде функции, а код, вычисляющий время заполнения списка, - в виде декоратора. frorn tirne irnport tirne # 1 def profiler(fn): def wrapper(*args, **kwargs): # # 2 3
Урок 6. Функции 121 begin = tirne () result = fn(*args, **kwargs) end = tirne () print(f'Bpeмя выполнения: {end - begin:.4f) return result return wrapper # # # # 4 5 6 7 # в @profiler def list filling_with_plus(end, begin=l, *, step=l): 1st = [) for i in range(begin, end + 1, step): 1st += [i) # 9 list filling_with_plus(lOOOOOOO) # 10 с.') Для вычисления времени мы применим здесь функцию tirne () из одноименного модуля ( поз. 1 в листинге 6.1 ). I января 1970 года 1 • Она возвращает количество секунд, прошедших с полуночи Объявляем декоратор, дав ему имя profiler () (поз. 2). В его теле объявляем вло­ женную функцию, которая станет своего рода оберткой вокруг декорируемой функции и добавит в нее дополнительную функциональность мени выполнения (поз. 3). - вычисление вре­ Обычно такой «обертке» дают имя wrapper (). В теле «обертки» вызовом функции tirne () перед вызовом декорируемой функции (поз. ченных «оберткой» параметров (поз. 5) получаем текущее значение времени 4), вызываем ее с передачей всех полу­ и получаем текущее значение времени после вызова декорируемой функции (поз. 6). Далее останется вычислить время выполнения декорируемой функции, вычтя первое значение времени из второго, и вывести результат на экран (поз. 7). Не забываем вернуть «обертку» из декоратора (поз. 8). Далее эта «обертка» всегда будет вызываться вместо декорируемой функции. Объявляем функцию list_filling_with_plus(), которая и будет заполнять список числами, указав у нее только что объявленный декоратор profiler () (поз. зываем эту функцию, задав диапазон чисел 9). И вы­ 1-10000000 (поз. 10). На компьютере автора программа выдала следующий результат: Время выполнения: 1.0601 с. У функции можно указать произвольное количество декораторов, записав каждый в отдельной строке: @decoratorl @decorator2 @decoratorЗ 1 Этот момент времени носит название «начало эпохи UNIX».
Часть 122 /. Язык Python: основные инструменты def func(): Декораторы будут выполнены в порядке, обратном порядку их указания. Так, в приведенном decorator2 () 6. 7. примере и, наконец, сначала выполнится декоратор decoratorЗ (), потом - decoratorl (). Сообщения об ошибках в телах функций. Стек вызовов Если при выполнении тела функции возникла ошибка, исполняющая среда выведет стек вызовов. Стек вызовов 11 своего рода путь, который привел к тому месту в коде, где возникла ошибка. Рассмотрим следующий код: def funcl(): prnt ( 'funcl ' ) def func2(): funcl () func2 () Здесь присутствует вызов функции func2 (), которая вызывает функцию funcl (). В теле функции funcl () присутствует синтаксическая ошибка - имя встроенной функции print () написано неправильно. При попытке выполнить приведенный код исполняющая среда выведет вот что: Traceback (rnost recent call last): File "d:\work\6.1.py", line 3, in <rnodule> func2 () ~~~~~"" File "d:\work\6.1.py", line 2, in func2 def func2 () : funcl () File "d:\work\6.1.py", line 1, in funcl def funcl () : prnt ( 'funcl' ) NarneError: narne 'prnt' is not defined. Did you rnean: 'print'? Стек вызовов отображается после строки: Traceback (rnost recent call last): В самом начале присутствует выражение, вызывающее функцию func2 (), стека вызовов, того самого «пути», который привел к ошибке: начало
Урок б. Функции 123 File "d:\work\6.1.py", line 3, in <module> func2 () Далее показано funcl () , - выражение в теле функции func2 (), вызывающее функцию следующий шаг стека вызовов, «пути» к ошибке. Кроме того, в первой строке после слова in показано имя функции, в которой присутствует это выраже­ ние (подчеркнуто). File "d:\work\6.1.py", line 2, in func2 funcl () И наконец, представлено выражение в теле функции funcl (), непосредственно вы­ звавшее ошибку, конец стека вызовов, где «путь» к ошибке завершился: - File "d:\work\6.1.py", line 1, in funcl prnt ( 'funcl' ) Во многих случаях просмотр стека вызовов позволяет выяснить, какие обстоятель­ ства, проявившиеся при выполнении программы, привели к возникновению ошибки. 6.8. Что еще нужно знать о функциях ♦ Объявление функции может располагаться в произвольном месте кода, в том числе и перед вызовами этой функции. Как правило, код объявлений функций ставят в начале модуля и отделяют от остального кода пустой строкой. Объявления отдельных функций также разде­ ляют пустыми строками. ♦ Объявление функции, тело которой состоит из одного или нескольких коротких выражений, можно записать в одну строку: def division(a, Ь): result =а/ Ь; print(f'{a} / {Ь} def division2(a, Ь=2): print(f'{a} / {Ь) ={а/ Ь}') ♦ {result}') Функция, не содержащая оператор возврата return (см. разд. 6.2.1), неявно воз­ вращает значение None. Разумеется, это значение совершенно бесполезно, и его всегда игнорируют. ♦ Объект функции поддерживает три полезных атрибута: имя текущей функции в виде строки; • _ name_ - • _qualname_ дах и путях • module строки. - путь к текущему методу в виде строки (подробности о мето­ в разд. 8.2.2); имя модуля, в котором объявлена текущая функция, в виде
Часть 124 /. Язык Python: основные инструменты Пример: >>> def fn(): pass name >>> fn. 'fn' ♦ Рекурсия - это вызов функции самой себя. Рассмотрим пример такой функции: >>> def recursive(count): if count == О: return ';:) print('+' * count) recursive(count - 1) >>> recursive(S) +++++ ++++ +++ ++ + Эта функция вызывает сама себя, каждый раз передавая значение ее параметра, уменьшенное на единицу. Когда функция получает с параметром число О, она завершает работу, тем самым прерывая цикл рекурсивных вызовов. В Python функция может вызвать сама себя не более 1ООО раз. Превышение это­ го количества рекурсивных вызовов приведет к ошибке типа RecursionError. 6.9. Самостоятельное упражнение Дополните программу из листинга ски соответственно посредством (см.разд. 5.1.2.2) 6.1 еще двумя функциями, заполняющими спи­ последовательных и спискового включения (см.разд. лучившихся трех функций выполнится быстрее заполнения списка эффективнее (у автора книги сковое включение). - вызовов 5.1.4). метода append ( ) Выясните, какая из по­ и соответственно какой способ функция, использовавшая спи­
Урок7 Классы и объекты Объект это сложное значение, содержащее набор входящих в него простых зна­ - чений и инструменты для работы с ними. Простые значения хранятся в атрибутах (переменных, принадлежащих объекту), а инструменты реализованы в методах (функциях, принадлежащих объекту). Класс - это образец для создания объектов определенного вида. Задает набор ат­ рибутов и методов для объектов, которые будут созданы на основе этого класса. 7 .1. Объекты 7 .1.1. Создание объектов Объект заданного класса создается выражением следующего формата: <к.ласе> ( [ <параметр 1>, <параметр 2>, . . ., <параметр N>] ) Создание объекта напоминает вызов функции. Значения параметров будут занесены в атрибуты создаваемого объекта. Выражение возвращает ссылку на созданный объект, которую следует присвоить какой-либо переменной или передать в параметре какой-либо функции. Примеры: >>>#Создаем целое число - объект класса int - на основе строки с числом >>> n = int ( '12345') >>> n 12345 >>>#Создаем значение даты - >>>#на основе заданных года, объект класса date из модуля datetime - номера месяца и числа >>> from datetime import date >>> d = date(2025, 7, 7) >>> d datetime.date(2025, 7, 7) 7 .1.2. Обращение к атрибутам и вызов методов Обращение к заданному атрибуту указанного объекта записывается в формате 1 : <обьект>.<атрибут> 1 Этот формат называют записью с точкой, или венгерской нотацией.
Часть 126 /. Язык Python: основные инструменты Применив эту запись, можно как считать значение атрибута, так и присвоить ему новое значение. Пример: >>>#Извлечем год из даты, обратившись к атрибуту year >>> d.year 2025 Для вызова метода у объекта применяется выражение формата: <объект>. <метод> ( [ <параметр 1>, <параметр 2>, . . . , <параметр N>]) Оно похоже на вызов обычной функции, только содержит указание на объект. Примеры: >>> # Заменим у даты число на 27, вызвав метод replace () >>> d = d.replace(day=27) >>> d datetirne.date(2025, 7, 27) >>> # Узнаем номер дня недели, вызвав у даты метод isoweekday () >>> # (день 7 - это воскресенье) >>> d.isoweekday() 7 Обращение к несуществующему атрибуту или вызов несуществующего метода приведет к ошибке типа AttributeError. 7.1.3. Объектные и классовые атрибуты и методы До этого момента мы имели дело с объектными атрибутами и методами. Объектный атрибут 11 Объектный метод принадлежит объекту и хранит составную часть объекта. принадлежит объекту и манипулирует объектом. - Так, атрибут year, методы replace () date, - и isoweekday (), поддерживаемые классом объектные. Однако многие классы также содержат классовые атрибуты и методы. Классовый атрибут принадлежит классу и хранит какое-либо значение, характер­ - ное для самого класса. Классовый метод - принадлежит классу. Обычно создает объекты этого класса, на­ строенные определенным образом. Обращение к классовому атрибуту и вызов классового метода осуществляются через класс (хотя можно сделать это и через объект этого класса). Класс da te поддерживает классовые атрибуты rnin и rnax, хранящие соответственно наименьшую и наибольшую даты, которые этот класс способен обработать: >>>#Обращаемся к классовым атрибутам через класс date >>> date.rnin, date.rnax (datetirne.date(l, 1, 1), datetirne.date(9999, 12, 31))
Урок 7. Классы и объекты 127 >>>#Обращаемся к ним же через объект этого класса >>> d.min, d.max (datetime.date(l, 1, 1), datetime.date(9999, 12, 31)) А классовый метод today () того же класса возвращает текущую дату: >>> date. today (), d. today () (datetime.date(2025, 7, 7), datetime.date(2025, 7, 7)) Говоря об атрибутах или методах, всегда имеют в виду объектные атрибуты и мето­ ды. Если речь идет о классовых атрибутах и методах, всегда явно указывают, что они 7.2. - классовые. Классы Программист может создать произвольное количество собственных классов для своих нужд и работать с ними так же, как и со встроенными в исполняющую среду или реализованными в стандартной библиотеке. 7.2.1. Объявление класса Объявление класса 11 выражение, которое описывает класс, создаваемый программи­ стом. Записывается в формате: class <имя класса>: <объявления объектных методов> Имя класса должно быть уникальным в пределах текущего модуля и удовлетворять тем же требованиям, что и имена переменных (см. разд. Объявления (см.разд. 1.4). объектных методов записываются так же, как и объявления функций 6.2.1), только с отступами слева (поскольку представляют собой блок). Первым параметром каждый метод должен принимать ссылку на текущий объект. Традиционно этому параметру дают имя self. При вызове метода ссылку на текущий объект указывать не следует. Ее передаст сама исполняющая среда. Объектные атрибуты следует создавать в телах методов, объявленных в классе, - простым присваиванием значений этим атрибутам. В качестве примера объявим класс Rectangle, хранящий сведения о прямоуголь­ нике. Класс получит set_sides (<ширина>, атрибуты <высота>) width (высота) и height (ширина), методы (задает размеры прямоугольника) и get_square () (возвращает его площадь). Вот код объявления: >>> class Rectangle: def set_sides(self, width, height): self.width = width self.height = height
Часть 128 /. Язык Python: основные инструменты def get_square(self): return self.width * self.height Проверим этот класс в работе, создав его объект: >>> rl = Rectangle() >>> rl.set sides(20, 50) >>> rl.width, rl.height, rl.get_square() (20, 50, 1000) Создадим еще один объект этого же класса: >>> r2 = Rectangle() >>> r2.set_sides(1000, 400) >>> r2.get_square() 400000 Записав в выражении, объявляющем класс, вместо объявлений методов оператор pass, мы создадим пустой класс, который ничего не делает: >>> class Empty_Class: pass 7.2.2. Конструктор и деструктор Объявленный нами класс неудобен: после создания объекта приходится вызывать у него метод set_ s ides ( ) , чтобы задать ширину и высоту. Удоб нее указывать их непосредственно при создании объекта вразд. - в круглых скобках (как показано 7.1.1). Для этого необходимо лишь объявить в классе конструктор. Конструктор - метод, автоматически вызываемый при создании любого объекта текущего класса. Все параметры, указанные в выражении создания объекта в круглых скобках, передаются конструктору. Конструктор должен иметь имя _ ini t _ (). Пример класса Rectangle, содержащего конструктор: >>> class Rectangle2: init (self, width, height): def self.width = width self.height = height def get square(self): return self.width * self.height >>> >>> rЗ = Rectangle2(20, 50) rЗ.get_square() 1000 Очевидно, что классом, содержащим конструктор, пользоваться удобнее.
Урок 7. Классы и объекты 129 На заметку Программисты зачастую, имея в виду выражение создания объекта, говорят: «вызов конструктора». Мы тоже будем так говорить в дальнейшем. Деструктор - метод, автоматически вызываемый перед уничтожением текущего объекта. Деструктор должен иметь имя () и принимать единственный параметр - ctel ссьшку на текущий объект. Пример простейшего класса с деструктором: >>> class Cls: def del (self): print ( 'Вызван деструктор') >>>#Создаем объект этого >>> с = Cls () >>> # ... и удаляем >>> del с его. класса ... Перед удалением объекта будет вызван деструктор. Вызван деструктор В деструкторах обычно выполняется освобождение занятых ранее ресурсов (на­ пример, закрьrrие открытых файлов). Чтобы закрепить пройденное, напишем программу (листинг 7 .1 ), которая будет пе­ реводить значения температуры из шкалы Фаренгейта в шкалу Цельсия и наоборот. Для собственно перевода температуры объявим особый класс. l.1. Проrрамма дnR nеревода 3Н8'18НМЙ 18М11ературы из wкаnы Фаренrейта • ...., Цепа.см" наоборот class Convertor: def init (self, value, scale): if scale == '2': self.value (value - 32) * 5 / 9 else: self.value = value def get_value(self, scale): if scale == '2': return self.value * 9 / 5 + 32 else: return self.value # # # 1 2 3 # 4 # 5 # # 6 7 # 8 9 print('Пepeвoд значения температуры из одной шкалы в другую') source scale = inрut('Укажите исходную шкалу: 'l) Цельсия или 2) ' Фаренгейта if not source scale: source scale = '2' source_value = float(input('Bвeдитe переводимое с= Convertor(source_value, source scale) (<Enter>=2): значение: ')) ') #
Часть 130 dest_scale = inрut('Укажите целевую шкалу: '1) Цельсия или 2) /. Язык Python: основные инструменты ' (<Enter>=l): ') Фаренгейта if not dest scale: dest scale = '1' print(c.get_value(dest scale)) input листинге 1в (поз. # 12 <Enter>') ('Для завершения программы нажмите Мы объявляем здесь класс 7 .1 ). # 10 # 11 Convertor, который и будет переводить температуру Его конструктор станет принимать два параметра исход­ - ное значение температуры и обозначение шкалы, в которой оно представлено: Цельсия или '2' - 'l' - Фаренгейта (поз. 2). В теле конструктора, если получено значение в шкале Фаренгейта, переводим его в шкалу Цельсия и сохраняем в атри­ value, а если в стоградусной шкале - сохраняем без преобразований (поз. 3). Метод get value () получит с параметром обозначение шкалы (поз. 4) и выдаст зна­ буте чение температуры: если получено обозначение '2' его в шкалу Фаренгейта, а если обозначение '1 1 - предварительно переведя как есть (поз. 5). Сначала запрашиваем у пользователя обозначение шкалы, в которой будет введено исходное значение температуры (поз. сто нажал клавишу (поз. 7). <Enter>, 6). Если пользователь ничего не ввел и про­ устанавливаем обозначение '2' - шкала Фаренгейта Так мы сделаем нашу программу немного дружественнее к пользователю. Далее запрашиваем само переводимое значение, не забыв преобразовать его в ве­ щественное число (поз. 8). После чего на основе введенных данных создаем объект только что объявленного класса Convertor (поз. 9). Спрашиваем у пользователя, в какую шкалу он хочет преобразовать занесенную температуру (поз. 1О). Если пользователь ничего не ввел и лишь нажал устанавливаем обозначение '1 ', т. е. стоградусную шкалу (поз. После чего осталось лишь вызвать метод get _ val ue () объекта класса Converter, созданного ранее, и вывести в консоли возвращенный им результат (поз. 7.2.3. <Enter>, 11 ). 12). Наследование классов Наследование это создание одного класса (называемого производным, или под­ - классшw) на основе другого (базового, или суперкласса). Производный класс получает (наследует) все атрибуты и методы базового класса. 7.2.3.1. Объявление производного класса Объявление класса, производного от заданного базового, записывается в формате: class <имя производного класса>(<базовый класс>): <объявления объектных методов> Rectangle2 класса RectangleЗ, содержащего допол­ get _ sides _ length () (возвращает длину периметра текущего пря­ моугольника), get _ sides () (возвращает список с размерами сторон) и get _ info () Пример производного от класса нительные методы (возвращает строку со сведениями о прямоугольнике):
Урок 7. Классы и объекты 131 >>> class Rectangle3(Rectangle2): def get_sides_length(self): return self.width * 2 + self.height * 2 def get_sides(self): return [self.width, self.height] def get_info(self): # \xd7 - это знак умножения (х) return f'{self.width} \xd7 {self.height}' >>> r4 = Rectangle3(40, 30) >>> r4.get_sides_length() 140 >>> r4.get_sides() [ 40, 30] >>> r4.get_info() '40 х 30' >>>#Производный класс >>>#базового RectangleЗ поддерживает все методы Rectangle2, класса в частности get_square() >>> r4.get_square() 1200 7 .2.3.2. Перекрытие и переопределение методов Перекрытие метода - полная замена метода, унаследованного от базового класса, в производном классе. Выполняется повторным объявлением перекрываемого метода в производном классе. На практике, однако, чаще выполняется переопределение. Переопределение метода - дополнение или изменение функциональности метода, унаследованного от базового класса, в производном классе. Выполняется так же, как и перекрытие, только в нужное место тела переопределяемо­ го метода вставляется вызов того же метода базового класса. Вызов требуемого метода базового класса записывается в следующем формате: super () . <метод> ( [<параметр 1>, <параметр 2>, . . ., <параметр N>] ) Ссылку на текущий объект первым параметром передавать не следует. Для примера объявим класс Parallelepiped, производный от Rectangle3. Переопре­ делим его конструктор, предписав ему получать с дополнительным третьим пара­ метром толщину фигуры. Дополним класс атрибутом thickness (толщина) и мето­ get_capacity() (возвращает объем текущей фигуры). Перекроем метод get_ sides () и переопределим метод get _ info (), чтобы они возвращали все три размера дом фигуры. Вот готовый код нового класса: >>> class Parallelepiped(Rectangle3): # Переопределяем конструктор init (self, width, height, thickness): def
Часть 132 /. Язык Python: основные инструменты # Вызываем конструктор базового класса, передав ему # полученные с параметрами ширину и высоту super(). init (width, height) # Сохраняем тоmцину self.thickness = thickness def get capacity(self): returп self.get_square() * self.thickness # Этот метод перекрываем def get_sides(self): return [self.width, self.height, self.thickness] # А этот - переопределяем def get info(self): # Вызываем метод базового класса s = super() .get_info() return f'{s} \xd7 {self.thickness}' >>> р = Parallelepiped(40, 30, 5) >>> p.width, p.height, p.thickness (40, 30, 5) >>> p.get_square(), p.get_sides length(), p.get_capacity() (1200, 140, 6000) >>> p.get_info() '40 х 30 х 5' Для практики напишем новую версию программы из листинга 7 .1, добавив ей под­ держку темпераrурной шкалы Реомюра. Дадим этой шкале обозначение '3 '. Фактически нам понадобится лишь объявить класс convertor2, производный от Convertor, переопределить в нем унаследованные конструктор и метод get_ value () и изменить текст выводящихся сообщений. Ключевой фрагмент кода новой про­ граммы, содержащий объявление класса convertor2, приведен в листинге class Convertor2(Convertor): def init (self, value, scale): if scale == '3': self.value = value * 1.25 else: super(). init (value, scale) def get_value(self, scale): if scale == '3': return self.value * .8 else: return super() .get_value(scale) 7 .2.
Урок 7. Классы и объекты 7 .2.3.3. 133 Множественное наследование наследование производного класса сразу от не­ Множественное наследование 11 скольких базовых классов. Выражение объявления производного класса с множественным наследованием от нескольких базовых классов имеет такой вид: class <имя пр. класса>(<баз. 1>, кл. <баз. кл. 2>, . . . , <баз. кл. N>): <объявления объектных методов> При обращении в производном классе к методу, объявленному в одном из базовых классов, эти классы просматриваются в том порядке, в котором они указаны в выра­ жении объявления: >>> class Cls: def methodl(self): print('Cls.methodl() ') def method2(self): print('Cls.method2() ') >>> class Cls2: def method2(self): print('Cls2.method2() ') def methodЗ(self): print ( 'Cls2 .methodЗ () ') >>> class Cls3(Cls2, Cls): pass >>> >>> ClsЗ () .methodl () Cls. methodl () с = с >>>#При попьгrке вызвать метод >>>#из класса Cls2, method2() будет вызван метод поскольку он стоит раньше в перечне базовых классов >>> c.method2 () Cls2 .method2 () >>> c.methodЗ() Cls2 .methodЗ () Если в производном классе необходимо унаследовать метод из какого-то опреде­ ленного базового класса, в составе объявлений объектных методов достаточно поста­ вить строку следующего формата: <имя наследуемого метода>= <базовый класс>.<наследуемый метод> Пример: >>>#Наследуем метод method2() >>> class Cls4(Cls2, Cls): method2 = Cls.method2 из базового класса Cls
Часть 134 /. Язык Python: основные инструменты »> с = Cls4 () >>> c.method2 () Cls .method2 () 7.2.3.4. Указание декораторов функций у методов У методов можно указывать декораторы функций (см.разд. 6. 6) 1: class Some Class: @profiler def some method(self): 7 .2.4. Классовые атрибуты и методы. Статические методы Для создания классового атрибута (см.разд. 7.1.3) в выражении объявления класса в состав объявлений объектных методов следует добавить строку формата: <имя классового атрибута>= <его изначальное значение> В теле объектного метода обратиться к классовому атрибуту можно как по имени класса: class Some Class: some attr = 100 def some_method(self): Some Class.some attr = 200 так и воспользовавшись атрибутом _ class _ текущего объекта, который хранит ссылку на объявление текущего класса. Пример класса Cnt с классовым атрибутом count, хранящим количество сущест­ вующих объектов этого класса: >>> class Cnt: count = о def init (self): self. class .count += 1 def del (self): self. class .count - 1 >>> cl = Cnt () >>> Cnt.count 1 1 Поскольку метод - это частный случай функции.
Урок 7. Классы и объекты 135 >>> с2 = Cnt () >>> Cnt.count 2 >>> del cl >>> Cnt.count 1 Классовый метод, объявляемый в классе, должен: ♦ быть помеченным встроенным декоратором classmethod (); ♦ принимать первым параметром ссылку на текущий класс. Традиционно этому параметру дают имя cls. Пример производного от класса make_ square (<размер Rectangle2 класса Rectangle4 с классовым методом стороны>), возвращающим квадрат с заданным размером сто­ роны. >>> class Rectangle4(Rectangle2): @classmethod def make_square(cls, side): # Создаем объект текущего # через параметр cls return cls(side, side) класса, обратившись к классу >>> r = Rectangle4.make_square(40) >>> r.width, r.height (40, 40) Статический метод раметром ссылку - похож на классовый, только не получающий с первым па­ на текущий класс. Помечается встроенным декоратором staticmethod (): >>> class Cls: @staticmethod def method(a, Ь): print(a, Ь) >>> Cls.method(l, 7) 1 7 7.2.5. Закрытые атрибуты и методы До сих пор мы имели дело только с открытыми атрибутами и методами. Открытые атрибут и метод 11 доступны отовсюду: как внутри текущего класса, так и вне его. Однако любой атрибут или метод можно сделать закрытым. Закрытые атрибут и метод - доступны только внутри текущего класса. Попытка получить доступ к закрытому атрибуту или методу извне класса приведет к ошибке типа AttributeError.
Часть 136 /. Язык Python: Превратить атрибут или метод в закрытый очень просто 11 его имя префиксом в виде двух символов подчеркивания основные инструменты достаточно предварить U- Вот код класса RectangleS, аналогичного классу Rectangle2, но содержащего закры­ тые атрибуты _width и _height, а также закрытый метод _set_sides (<ширина>, <высота>), записывающий в текущий объект заданные значения ширины и высоты: >>> class Rectangle5: init (self, width, height): def self._set_sides(width, height) set sides(self, width, height): def self. width = width self._height = height def get square(self): return self. width * self._height >>> r = Rectangle5(200, 300) >>> r.get_square() 60000 >>>#Попытка обратиться к >>> r. закрытому атрибуту приведет к ошибке width AttributeError: 'Rectangle5' object has no attribute ' 7 .2.6. width' Свойства Свойство - более развитый аналог атрибута, представляющий собой комбинацию геттера, сеттера и (или) делетера. Геттер - метод, вызываемый при попытке считать значение свойства. Не должен принимать параметров и должен возвращать результат, который и станет значением свойства. Помечается встроенным декоратором property (). Если отсутствует, свойство не будет доступно на чтение. Попытка присвоить такому свойству значение приведет к ошибке типа AttributeError. Обычно для хранения значения свойства в классе создается закрытый атрибут. В таком случае геттер получает значение свойства из этого закрытого атрибута. Также значение свойства может вообще нигде не храниться, а вычисляться гетте­ ром на основе значений из других атрибутов. Сеттер - метод, вызьmаемый при попытке присвоить свойству новое значение. Должен принимать с единственным параметром новое значение, присваиваемое свой­ ству. Не <геттер>. должен возвращать результата. Помечается декоратором формата setter (). Если отсутствует, свойство не будет доступно на запись, и попытка считать его зна­ чение приведет к ошибке типа AttributeError.
Урок 7. Классы и объекты 137 Обычно сеттер присваивает новое значение свойства закрытому атрибугу, преду­ смотренному для его хранения. Также он может выполнять какие-либо дополни­ тельные действия (например, изменять значения других атрибутов). Делетер - метод, вызываемый при попытке удалить свойство с помощью оператора del. Не должен ни принимать параметров, ни возвращать результата. Помечается декора­ тором формата <геттер>. deleter 1). Если отсутствует, свойство будет нельзя удалить, и попытка сделать это приведет к ошибке типа AttributeError. Как правило, делетер присваивает закрытому атрибугу, хранящему значение свой­ ства, какое-либо «пустое» значение (None, число О, пустую строку, пустой список и т. п.). Также он может выполнять различные дополнительные действия. Ге1:ер, сеттер и делетер должны иметь одно и то же имя, которое и станет именем 11 своиства. Объявим класс cls, содержащий свойство prop с геттером, сеттером и делетером. Дr~я хранения значения свойства создадим закрытый атрибут _prop. Геттер перед выдачей значения будет приводить его к нижнему регистру, сеттер перед сохране­ нием - _prop обрезать начальные и конечные пробелы, а делетер - заносить в атрибут пустую строку: >>> class Cls: @property def prop(self): return self._prop.lower() @prop.setter def prop(self, value): self._prop = value.strip() @prop.de1eter def prop(self): self._prop »>с= Cls() >>>#Присваиваем значение >>> c.prop = ' свойству prop - вызывается сеттер pYtHoN >>>#Получаем значение свойства prop - вызывается геттер >>> c.prop 'python' >>>#Удаляем свойство prop - вызывается делетер >>> del c.prop >>> c.prop Объявим класс Rectangleб, аналогичный классу Rectangle2, только с доступным лишь на чтение свойством square:
Часть 138 /. Язык Python: основные инструменты >>> class Rectangleб: def init (self, width, height): self.width, self.height = width, height @property def square(self): return self.width * self.height >>> r = Rectangle6(150, 200) >>> r.square 30000 7.2.7. Добавленные атрибуты Добавленный атрибут - произвольный атрибут, добавленный в один из объектов какого-либо класса. Принадлежит исключительно этому объекту. Создается простым присваиванием ему значения. Пример: >>>#Создаем объект >>> r = Rectangle6(150, 200) >>>#Создаем у него добавленный >>> r.desc >>> r.desc атрибут desc 'Земельный участок' = 'Земельный участок' >>>#У других объектов того же класса такого атрибута нет >>> r2 = Rectangleб(.1, .2) >>> r2.desc AttributeError: 'Rectangleб' object has no attribute 'desc' Добавленный атрибут можно удалить оператором del: >>> del r.desc 7.2.7.1. Ограничение набора создаваемых атрибутов Можно задать перечень атрибутов, которые могут быть созданы в объектах какого­ либо класса - как обычных, создаваемых в коде методов, так и добавленных. Соз­ дать атрибут, не приведенный в этом перечне, будет невозможно - это приведет к ошибке типа AttributeError. Последовательность имен создаваемых атрибутов присваивается классовому атри­ буту _slots_. Имена должны быть представлены в виде строк. Пример класса Rectangle7, в котором доступны для создания лишь атрибуты width, height И desc: >>> class Rectangle7: slots ( 'width', 'height', 'desc')
Урок 7. Классы и объекты 139 >>> r = Rectangle7(150, 200) >>> r.width 150 >>> r.desc = 'Земельный участок' >>>#Создать >>> атрибут, r.addendшn = не приведенный в списке, нельзя 'Большой' AttributeError: 'Rectangle7' object has no attribute for setting new attributes dict and no Перечень атрибутов из классового атрибута _ slots _ 'addendшn' наследуется всеми подклас­ сами. Подкласс может иметь свой собственный классовый атрибут _slots_, хра­ нящий перечень допустимых атрибутов, который будет объединен с перечнем из супер класса. 7.2.8. 11 Вложенные классы Вложенный класс - класс, объявленный внутри другого класса (родительского). Обратиться к объявлению вложенного класса можно через ссылку на текущий объект, которая передается любому методу через первый параметр (self). Пример: >>> class Outer Class: # Объявляем вложенный класс class Inner Class: init (self, а): def self.a = а init (self, а, Ь): def # В конструкторе родительского класса # вложенного класса self.Inner_Class(a) self.inner self.b = Ь >>>ос= создаем объект Outer_Class(l0, 20) >>>#Обращаемся к атрибуту родительского класса >>> ос.Ь 20 >>>#Обращаемся к атрибуту вложенного класса >>> oc.inner.a 10 Объект вложенного класса можно создать и в обычном коде, обратившись к нему через одноименный атрибут родительского класса: >>> ic = Outer_Class.Inner_Class(l0) >>> ic.a 10
Часть 140 7.2.9. /. Язык Python: основные инструменты Класс как значение Объявление любого класса само по себе является значением - объектом класса type: >>> class Cls: pass >>> type(Cls) <class 'type'> Любой класс можно присвоить какой-либо переменной и использовать ее дr1я соз­ дания объектов этого класса: >>> some class = Cls >>>с= some_class() Любой класс также можно передать с параметром какой-либо функции, которая использует его дr1я работы. 7 .3. Декораторы классов Декоратор класса - функция, дополняющая или изменяющая функциональность класса, у которого указана (декорируемого класса). Декоратор должен принимать единственный параметр класс. Возвращать он должен ссылку на класс - - ссьшку на декорируемый декорируемый или какой-либо дру­ гой. В остальном декоратор класса аналогичен декоратору функции (см.разд. 6. 6). Пример декоратора add_ class _ info (), добавляющего в декорируемый класс метод get_ info () , который возвращает строку с именем класса: >>> def add_class info(cls): # Объявляем функцию, которая реализует добавляемьm метод def _get_info(self): return self. class_._qualname_ # Добавляем метод cls.get info return cls get_info() _get_info Проверим декоратор в деле: >>> @add- class - info class Clsl: pass >>> @add class info class Cls2: pass в декорируемый класс
Урок 7. Классы и объекты 141 >>> cl = Clsl(); с2 = Cls2() >>> cl.get_info(), c2.get_info() ('Clsl', 'Cls2') Что еще нужно знать 7 .4. об объектах и классах ♦ Выяснить, относится ли указанный объект к заданному классу, позволяет функ­ ция isinstance (): isinstance(<oбъeкт>, <класс>) Она возвращает True, если объект относится к классу, и False - в противном случае. Пример: >>> isinstance(l, int), isinstance (1, float) (True, False) >>> from datetime import date, time >>> d = date(2025, 7, 7) >>> isinstance(d, date), isinstance(d, time) (True, False) ♦ Объявление класса может располагаться в произвольном месте кода, в том числе и перед созданием первого объекта этого класса. Как правило, код объявлений классов ставят в начале модуля и отделяюr от остального кода пустой строкой. Объявления отдельных классов также разде­ ляют пустыми строками. ♦ Можно добавить произвольный метод в класс уже после объявления этого клас­ са - достаточно создать в объекте объявления класса атрибут и присвоить ему функцию, которая и реализует метод: # Объявляем класс class Some Class: # Объявляем функцию, которая реализует добавляемый метод ): def _new_method(self, # Добавляем в уже объявленный Some Class.new method = класс Нечто подобное мы проделали в разд. ♦ новый метод new method 7.3, когда писали декоратор класса. Объект любого класса поддерживает атрибут _ class_, хранящий ссылку на объявление класса, на основе которого создан этот объект. ♦ Объект объявления класса вает полезные атрибуты - как и объект функции (см.разд. _ name _, _ qualname _и_module_. 6. 7) - поддержи­
142 ♦ Часть /. Язык Python: основные инструменты Атрибут _qualname_ метода класса хранит путь к методу имен класса и метода, разделенных точкой. Атрибут _ - комбинацию из name _, как и в случае обычной функции, хранит лишь имя метода: >>> class Cls: def method(self): pass >>> Cls.method. ( 'method', 7 .5. 1. name , Cls.method._qualname 'Cls .method') Самостоятельные упражнения Добавьте в программу из листинга 7.2 поддержку температурной шкалы Кель­ вина. Дайте этой шкале обозначение '4 '. Объявите в программе производный от класса convertor2 класс-преобразователь ConvertorЗ, который будет выполнять необходимые преобразования. 2. Напишите новую версию той же программы. В ней объявите производный от класса ConvertorЗ класс преобразователя Convertor4 с классовым методом from_fahrenheit (<температура>). Метод будет принимать в качестве параметра температуру в градусах Фаренгейта и создавать и возвращать объект класса Convertor4.
Урока Модули и пакеты 8.1. Модули. Импорт и подключение Модуль это любой файл с программным кодом на языке - Python. Модуль может хранить объявления различных сущностей (переменных, функций и классов), стар­ товый код программы или то и другое. Стартовый модуль модуль, содержащий стартовый код программы. Этот модуль - и следует запускать, чтобы программа начала работать. По соглашениям стартовому модулю дают имя start.py или арр.ру. Разработчик программы может создать произвольное количество модулей, вынеся в них объявления различных переменных, функций и классов, - для собственного удобства. Стандартная библиотека представляет собой набор модулей, написанных Python разработчиками языка и содержащих реализацию различных программных инстру­ ментов. 8.1.1. Использование сущностей из другого модуля Чтобы использовать сущность переменную, функцию или класс, - - созданную в другом модуле, необходимо выполнить либо импорт этой сущности, либо под­ ключение всего модуля. Что именно сделать 8.1.1.1. Импорт сущностей Импорт - - дело вкуса. извлечение указанной суrщюсти (или сущностей) из заданного модуля с целью использовать ее в текущем модуле. Выполняется выражением следующего формата: from <модуль> import <сущность 1> [as <псевдоним <сущность 2> [as <псевдоним 1>], 2>], <сущность N> [as <псевдоним N>] У любой импортируемой сущности можно указать произвольно выбранный псевдо­ ним. Впоследствии к ней следует обращаться только по этому псевдониму. Пример импорта функций sqrt (), cbrt (), sin (), cos (), tan (), radians () (с указанием псевдонима rads ()) и переменной pi из модуля math:
Часть 144 /. Язык Python: основные инструменты from math import sqrt, cbrt, sin, cos, tan, radians as rads, pi а= sqrt(2) s = sin(rads(ЗO)) Перечень импортируемых сущностей можно взять в круглые скобки - тогда его можно будет разбить на подстроки, не пользуясь символами обратного слеша: from math import (sqrt, cbrt, sin, cos, tan, radians as rads, pi) При необходимости можно импортировать из заданного модуля все объявленные в нем сущности, записав выражение формата: from <модуль> import * Пример: from math import * а = sqrt (2) s = sin(rads(ЗO)) е = ехр(4) 1 log(4.8) Однако так поступают редко, поскольку множество импортированных сущностей отнимает слишком много памяти. 8.1.1.2. Подключение модулей Подключение - импорт заданного модуля (или модулей) целиком в виде представляю­ 1щего его объекта. Отдельные сущности, объявленные в подключенном модуле, будут доступны через одноименные атрибуть1 объекта модуля. Подключение выполняется выражением формата: import <модуль 1> [as <псевдоним 1>] , <модуль 2> [as <псевдоним 2>] , N> [as <псевдоним N>] ., <модуль У любого подключаемого модуля можно указать произвольный псевдоним. Тогда обращаться к этому модулю следует по этому псевдониму. Пример подключения модулей math, datetime и collections (с псевдонимом cols): import math, datetime, collections as cols math.sqrt(2) d = datetime.date(2025, 7, 7) proxy = cols.defaultdict(str, system='Linux', platform='nginx') а=
Урок 8. Модули и пакеты 8.1.2. 145 Указание сущностей, доступных извне 1По умолчанию все сущности, созданные в модуле, доступны извне 1та, так и при подключении модуля. как для импор- Сделать какие-либо сущности недоступными извне можно двумя способами: ♦ предварить имена сущностей символами подчеркивания # Эти функция def func(): (_): и класс будут доступны извне class Cls: # А эта переменная не будет доступна inпer = ♦ 1234 создать в модуле переменную с именем _ all _ и присвоить ей последователь­ ность имен сущностей, которые должны быть доступными извне - тогда все прочие сущности не будут доступны: = ('_func', 'Cls') all # Эта функция будет доступна извне, несмотря на подчеркивание в all # имени, поскольку она приведена в перечне из переменной def func(): # Этот класс class Cls: тоже # А эта переменная не будет, # из переменной inпer = 8.1.3. начале т. к. ее нет в перечне all 1234 Создание своих модулей С точки зрения Python модули из стандартной библиотеки и модули, созданные программистом, ничем не различаются. В выражениях импорта и подключения (см.разд. 8.1.1) имя модуля, созданного программистом, пишется без расширения. Пример импорта сущностей func, Cls и special из модуля utilities.py: from utilities import func, Cls, special В качестве практики перепишем программу-преобразователь величин длины из дюймов в сантиметры (см. листинг 2.2). Код, выполняющий собственно преобразо­ вание, оформим в виде функции coпvertor functions.py (листинг 8.1 ). упомянуrую функцию. () и вынесем в отдельный модуль В стартовом модуле start.py (листинг 8.2) импортируем
Часть 146 Листинг модуль 8.1. Программа-преобразователь functions.py /. Язык Python: основные инструменты из дюймов в сантиметры, def convertor(value): return value * 2.54 Листинг 8.2. Программа-преобразователь из дюймов в сантиметры, стартовый модуль start.py from functions import convertor input_value = float(input('Bвeдитe преобразуемую величину print ('Результат:', convertor (input_value), 'см.') input ( 'Для завершения программы нажмите <Enter>') в д.юймах: ')) Вынос функций и классов в другие модули, помимо удобства разработки, имеет еще одно преимущество. Если эти функции и классы потребуется использовать в какой-либо другой программе, достаточно просто скопировать модули, содержа­ щие их код, в папку с этой программой. 8.2. Пакеты Пакет - папка файловой системы, содержащая модули Python. Также может хранить вложенные пакеты. Вложенный пт.:ет - пакет, находящийся в другом пакете (называемом родитель­ ским). Код сложных программ почти всегда структурируется с применением пакетов - для удобства. 8.2.1. Создание пакетов Для формирования пакета Python необходимо создать папку, представляющую этот пакет, и записать в нее модуль инициализации. Модуль иницимизации - модуль, который хранится в пакете и, собственно, превра­ щает его в полноценный пакет Python. Должен иметь имя _init_.py. Модуль инициализации может быть как совершенно пустым, так и содержать ка­ кой-либо код, объявляющий различные сущности: переменные, функции и классы. 8.2.2. Импорт и подключение из пакетов. Пути сущностей Чтобы импортировать сущность из модуля, находящегося в составе какого-либо пакета, или подключить модуль, который входит в состав пакета, в соответствую­ щем выражении (см. разд. 8.1.1) необходимо указать путь к модулю.
Урок 8. 147 Модули и пвквты - последовательность имен пакетов, в которые последовательно вложен тре­ буемый модуль, и, возможно, имени самого этого модуля. Имена в пути разделяются Путь точками. Здесь можно проследить аналогию с путями к файлам и папкам файловой системы. Если путь: ♦ начинается: он отсчитывается от пакета, в котором находится текущий модуль; • с точки • с двух точек - от родительского пакета; • с трех точек - от родительского пакета родительского пакета и т. д.; • с любого другого символа - - от папки, из которой была запущена текущая Руthоn-программа; ♦ заканчивается: • именем модуля - будет выполнен импорт из модуля с заданным именем или его подключение; • именем пакета - импорт из модуля инициализации _init_.py, находящегося в этом пакете, или подключение этого модуля. Несколько примеров: # В стартовом модуле - импорт функции some_func из модуля # последовательно вложенного в пакеты packagel и package2 from packagel.package2.module3 import some func moduleЗ.py, # В стартовом модуле - импорт переменной special_value init .ру пакета packagel # из модуля инициализации from packagel import special_value packagel.module2.py - импорт класса Cool Class packagel.package2.module4.py from .package2.module4 import Cool_Class # В модуле # из модуля divider # В модуле packagel.package2.module3.py # из модуля packagel.package3.module5.py from .. package3.module5 import divider импорт переменной # В модуле packagel.package2.module3.py # из модуля packagel.module2.py from .. module2 import арр_ name импорт переменной арр_пате Подключение указанного модуля, находящегося в пакете с заданным путем, выпол­ няется выражением формата: from <путь к пакету> import <модуль> [as <псевдоним>] Например, вот так в стартовом модуле будет производиться подключение модуля package 1\package2\mod ulеЗ. ру:
Часть 148 from packagel.package2 import some_ func () /. Язык Python: основные инструменты moduleЗ moduleЗ. Для практики перепишем программу из листингов 8.1 и 8.2. Создадим пакет modules и переместим в него модуль functions.py. А число 2,54, на которое умножается исходная величина, присвоим переменной cms _ in_ inch, вынесем выражение, соз­ дающее эту переменную, в отдельный модуль constants.py, который запишем во вложенный пакет modules\utilities. Не забудем создать в обоих пакетах пустые моду­ ли инициализации _init_.py. Код исправленной программы приведен в листин­ гах 8.3-8.5. Листинг модуль 8.3. Программа-преобразователь modules\utllities\constants.py из дюймов в сантиметры, cms in inch = 2.54 Листинг модуль 8.4. Программа-преобразователь modules\funclions.py из дюймов в сантиметры, from .utilities.constants import cms in inch def convertor(value): return value * cms in inch Листинг 8.5. Программа-преобразователь из дюймов в сантиметры, модуль start.py from modules.functions import convertor input_value = float(input('Bвeдитe преобразуемую величину print ('Результат:', convertor(input_value), 'см.') input ( 'Дnя завершения программы нажмите <Enter>' ) 8.3. в дюймах: ')) Что еще нужно знать о модулях, импорте и подключении ♦ Для подключения модуля, находящегося в пакете, можно использовать выраже­ ние, описанное в разд. 8.1.1.2: import packagel.package2.module3 Однако для обращения к сущностям из модуля, подключенного таким образом, придется писать вот такую кавалькаду из атрибутов: packagel.package2.module3.some_func() Впрочем, при импорте можно указать более короткий псевдоним: import packagel.package2.module3 as some func () modЗ. modЗ
Урок В. Модули и пакеты ♦ 149 В каждом модуле самой исполняющей средой создается переменная name _, хранящая: • у модуля, подвергнутого импорту или подключению, - путь к этому модулю в виде строки; • у модуля, запущенного на выполнение, - строку '_main_'. Пример: from packagel import module2 from packagel.package2 import print (module2. name ) print(moduleЗ. name ) print ( name ♦ moduleЗ # # # Выведет: Выведет: Выведет: packagel .module2 packagel.package2.module3 main Можно написать модуль, который может быть использован как для импорта сущностей, так и в качестве стартового. В коде такого модуля ставится выраже­ ние ветвления с условием, удостоверяющимся, что значение переменной _name_ равно '_main_' (т. е. текущий модуль запущен, а не импортирован), и в этом случае выполняющим стартовый код: # Код, объявляющий различные # Текущий модуль запущен на if name main '· == ' # Стартовый код ♦ сущности выполнение? Переменная _file_, создаваемая в каждом модуле, хранит строку с полным путем к этому модулю. ♦ При первом импорте или подключении модуль загружается в память и остается там до самого завершения выполнения программы. При повторном импорте или подключении, выполненном в коде той же программы, модуль не загружается повторно. Это сделано ради повышения производительности. ♦ При первом импорте или подключении модуль (см.разд. 2.3), <изначальное имя модуля>.сруthоn-<версия 8.4. компилируется в байт-код сохраняемый в файле с именем вида: Python>.pyc Самостоятельное упражнение Перепишите программу-преобразователь (см. листинги дающий переменную cms _ in_ inches, из модуля 8.3-8.5), перенеся код, соз­ modules\utilities\constants.py в модуль инициализации пакета modules\utilities. Соответственно исправьте код modules\functions.py, который импортирует и использует эту переменную. модуля
Урок9 Исключения и их обработка. Обработчики контекстов В Python любая программная ошибка является фатальной - приводящей к аварий­ ной остановке выполнения программы. В том числе ошибка ввода - например, занесение вместо числа чего-либо, не являющегося числом. Поэтому Python предусматривает развитые инструменты для обработки возникаю­ щих ошибок, предотвращающие аварийный останов программы. 9.1. Исключения 9.1.1. Введение в исключения Ис,.:.1ючение - объект, представляющий возникшую ошибку. - Возбуждение исключения создание объекта исключения в ответ на возникшую ошибку. Исключения относятся к разным классам. Так, в случае синтаксической ошибки возбуждается исключение класса SyntaxError, при обращении к элементу последо­ вательности с несуществующим индексом - исключение класса обращении к элементу отображения с несуществующим ключом - IndexError, при KeyError, а при обращении к несуществующему атрибуту или вызове несуществующего метода - AttributeError. Как видим, тип ошибки - это класс представляющего ее исключения. Базовым классом для всех исключений является BaseException. От него наследует класс Exception, являющийся базовым для классов большинства исключений, включая упомянутые ранее. В ряде случаев исключение возбуждается, чтобы сообщить о возникновении какой­ либо нештатной ситуации. Так, метод index () класса list возбуждает исключение класса ValueError, ске ( подробности если элемент с заданным значением отсутствует в текущем спи­ в разд. - 5.1. 2. 4). Некоторые исключения являются сигналами для исполняющей среды Python. На­ пример, исключение класса GeneratorExit возбуждается итератором, чтобы сооб­ щить исполняющей среде, что генерируемая последовательность закончилась (рас­ сказ об итераторах - в разд. 5. 9).
Урок 9. Исключения и их обработка. Обработчики контекстов 151 Обработка исключений 9.1.2. Обработка исключения реагирование на возникновение исключения с сохранени­ - ем работоспособности программы. Обработчик исключения выражение, обрабатывающее исключения. Записывается - в формате: try: <блок except <блок except except, except, 1> [as 2> [as except, [as 1> <переменная>]]: выполняемый при возникновении исключения класса [<класс исключения N> <блок <переменная>]]: выполняемый при возникновении исключения класса [<класс исключения <блок except в котором могут возникать исключения> try, [<класс исключения 2> <переменная>]]: выполняемый при возникновении исключения класса N> [else: <блок else, выполняемый, если исключение не возникло>] [finally: <блок finally, завершаЮIЩ,1й, выполняемый в любом случае>] При возникновении исключения в одном из выражений блока try его выполнение будет немедленно прервано и выполнится соответствующий блок except. Далее будет исполнен блок finally (если он присутствует в выражении) и начнет выпол­ няться код, следующий за обработчиком. Выполнение блока try после возникно­ вения исключения не возобновится. Пример (исключение класса ZeroDivisionError сообщает о делении на О): >>> try: # Здесь будет возбуждено исключение класса = l / О except ZeroDivisionError: print ('Деление на О! ' ) else: print ('Результат:', а) finally: print ( 'Бьшолнение обработчика а Деление на О! Быполнение обработчика закончено') закончено >>> try: #Ав а этом случае исключение не = 1 / 2 Результат: 0.5 Бьmолнение обработчика закончено возникнет ZeroDivisionError
Часть 152 /. Язык Python: основные инструменты Переменной, указанной после ключевого слова as, будет присвоен объект возникше­ го иск !юче1111·1. В соответствующем блоке из него можно извлечь текстовое описа­ ние исключения. Пример: >>> try: а = 1 / О except Exception as ехс: print(exc. class _ _ name print(exc) ZeroDivisionError division Ьу zero Будут обработаны исключения не только класса, указанного после ключевого слова except, но и производных от него классов. Так в приведенном примере было успешно обработано исключение класса zeroDivisionError, являющегося произ­ водным от указанного класса Exception. Если после ключевого слова except не задать класс исключения, будут обрабаты­ ваться исключения любых классов: >>> try: а= '123' - 12 except: print ('Ошибка' ) Ошибка Однако такие выражения лучше не использовать, поскольку можно случайно обра­ ботать исключение, которое не сообщает об ошибке, а уведомляет о чем-либо исполняющую среду (наподобие упомянутого в разд. 9.1 исключения класса GeneratorExit). А это чревато нарушением работы программы. Обработчики исключений можно вкладывать друг в друга: try: try: except NameError: рrint('Несуществующая except TypeError: print ('Неподходящий переменная, тип функция или класс') данных') except IndexError: рrint('Несуществующий элемент списка') элемент словаря') except KeyError: рrint('Несуществующий Исключение, не обработанное во вложенном обработчике, будет переправлено для обработки родительскому (внешнему) обработчику.
Урок 9. Исключения и их обработка. Обработчики контекстов 153 Любое исключение, не обработанное ни в одном из обработчиков, попадет в обработ­ чик по умолчанию, реализованный в исполняющей среде. Он выведет в консоли со­ общение об ошибке и прервет выполнение программы. Наша программа-преобразователь из дюймов в сантиметры (см. листинг 2.2) имеет существенный недостаток: если пользователь введет что-либо отличное от числа, возникнет ошибка типа valueError, которая приведет к аварийному останову. Давайте устраним этот недостаток. Код исправленной программы приведен в лис­ тинге 9.1. Листинг 9.1. Программа-преобразователь из дюймов в сантиметры, устойчивая к ошибкам input_value = inрut('Введите преобразуемую величину в ') дюймах: try: cms = float(input_value) * 2.54 except ValueError: print('Tpeбyeтcя ввести вещественное число') else: рrint('Результат:', cms, 'см.') finally: input ( 'Дпя 9.1.3. завершения программы нажмите <Enter>') Возбуждение исключений Чтобы самостоятельно возбудить какое-либо исключение, следует записать выра­ жение формата: raise [<объект исключения> 1 <класс исключения>] Объект исключения можно создать, записав выражение: <класс исключения> ( [ <описание ошибки или нештатной ситуации>] ) Описание указывается в виде строки. Если указать класс исключения, на его основе будет автоматически создан пустой объект исключения, не содержащий описания. Примеры: # Задан готовый объект исключения raise ValueError('Heдoпycтимoe значение') # Задан класс исключения raise NameError Если в блоке except обработчика исключения необходимо повторно возбудить обработанное исключение (например, для передачи его родительскому обработчи­ ку с целью дополнительной обработки), выражение, возбуждающее исключение, можно записать без задания объекта или класса исключения. Пример:
Часть 154 /. Язык Python: основные инструменты try: try: except IndexError as ехс: # Начинаем обработку исключения. # Возбуждаем его повторно, # обработчик для raise except IndexError as # чтобы передать в родительский завершения обработки. ехс: Заканчиваем обработку исключения 9.1.4. Объявление своих исключений Программист может объявить собственные классы исключений для своих нужд. Практически всегда они делаются производными от класса Exception. Этот класс уже содержит все необходимые атрибуты и методы, так что производные классы можно сделать пустыми. Пример объявления и использования собственного класса исключения: class SpecialError(Exception): pass try: if something_wrong(): raise SpecialError except SpecialError: print ('Ошибка') 9.2. Обработчики контекстов Часто приходится создавать какой-либо объект, выполнять с ним заданные дейст­ вия, после чего удалять вне зависимости от того, возникли ошибки при работе с ним или нет. Такого рода участки кода удобно оформлять в виде обработчиков контекстов. Обработчик контекста - выражение, которое создает заданный объект-контекст (или сразу несколько контекстов), выполняет над ним действия, приведенные в ука­ занном блоке, после чего автоматически удаляет. Контекст - объект, создаваемый в обработчике контекста. При его создании может выполняться заданный в нем инициализационный код (помимо конструктора), а пе­ ред уничтожением Преимущества ность - - указанный завершающий код (помимо деструктора). обработчиков контекста: компактность, наглядность и надеж­ поскольку контекст в любом случае будет гарантированно удален, а соот­ ветствующие завершающие действия - гарантированно выполнены.
Урок 11 9. Исключения и их обработка. Обработчики контекстов Менеджер контекста - 155 функция или объект, выдающая контекст. Обработчик контекста записывается в следующем формате: with <выражение, создающее контекст <выражение, создающее контекст 1> [as 2> [as <переменная 1>], 2>], <выражение, создающее контекст N> [as <переменная N>]: <блок, работаюIЩ,1Й с Каждое из выражений, <переменная созданными контекстами> создаl0Шl1х контекст, должно содержать обращение к какому­ либо менеджеру контекста - функции или классу. Контекст, созданный таким выражением, присваивается соответствующей переменной, которая становится дос­ тупной во всем коде, не только в блоке. Количество создаваемых контекстов не ограничено. Упомянутые переменные и соответственно хранящиеся в них контексты будут суще­ ствовать, пока выполняется блок. По завершении выполнения блока контексты бу­ дут автоматически удалены (с выполнением предусмотренных в них завершающих действий). Если переменная не указана, созданный соответствующим выражением контекст всё же будет существовать, пока выполняется блок, однако обратиться к нему из блока не получится. Функция open ( ) , предусмотренная в Python, открывает файл с указанным путем в заданном режиме (чтение или запись) и возвращает объект, представляющий от­ крытый файл. Этот объект предлагает ряд методов для чтения и записи данных'. Функция open () является менеджером контекста, а возвращаемые ею объекты - контекстами. Каждый такой объект содержит завершающий код, при удалении контекста закрывающий соответствующий файл. Напишем обработчик контекста, создающий файл d:\work\file.txt, записывающий в него три строки и закрывающий его: >>> with open('d:/work/file.txt', 'w', encoding='utf-8') as f: f. write ( 'Python\n') f.writе('лучший в мире\n') f. wri te ('язык программирования 1 ' ) 7 14 22 В качестве выражения, создающего контекст, мы записали вызов функции open () . Первым параметром указали путь к открываемому файлу (разделять сегменты пути в Python можно как обратными, так и прямыми слешами), вторым крытия (строка 'w' - 1 Подробный - режим его от­ файл открывается на запись, если он не существует, то будет рассказ об инструментах для работы с файлами выходит за рамки этой книги.
Часть 156 создан), третьим - /. Язык Python: основные инструменты обозначение текстовой кодировки. Созданный контекст ект, представляющий открытый файл, - - объ­ присваивается переменной f. Блок, работающий с контекстом, у нас содержит три выражения, записывающие строки в открытый файл. Запись выполняется вызовом у объекта файла метода write (), принимающего записываемую строку и возвращающего количество запи­ санных символов. Огкрытый файл будет автоматически закрыт после выполнения блока. Прочитаем и выведем содержимое только что созданного файла: >>> with open('d:/work/file.txt', 'r', encoding='utf-8') as f: print(' '.join(f.readlines())) Python лучший в мире язык nрограммирования! Режим ' r', заданный вторым параметром в вызове функции open ( ) , предписывает открытие файла для чтения. Метод readlines () объекта файла возвращает список из всех строк, прочитанных из файла. Мы объединяем этот список в строку мето­ дом join() (см.разд. 5.1.2.3) и выводим в консоли. Пример обработчика контекста, создающего сразу несколько контекстов: with context creatorl() as contextl, context_creator2() as context2,\ context_creatorЗ() as contextЗ, Context Creator4() as context4: Выражения, создающие контексты, можно заключить в круглые скобки - тогда их можно будет разбить на подстроки, не пользуясь символами обратного слеша: with (context_creatorl() as contextl, context_creator2() as context2, context creatorЗ() as contextЗ, Context Creator4() as context4):
ЧАСТЬ Язык 11 Python: расширенные инструменты :> Урок 1О. Функции и классы особой функциональности :> Урок 11. Регулярные выражения :> Урок 12. Установка и использование дополнительных библиотек :> Урок 13. Многопоточное и многопроцессное программирование :> Урок 14. Конкурентное программирование :> Урок 15. Аннотации типов и документирование кода. Дейтаклассы

Урок10 Функции и классы особой функциональности 10.1. Создание генераторов-функций Наряду с обычными функциями генераторы-функции (см.разд. Python предоставляет возможность создавать 5. 4.1). Генератор-функция объявляется так же, как и обычная функция (см.разд. 6.2). Для выдачи очередного элемента формируемой последовательности следует применять оператор возврата с приостановкой yield, записываемый в формате: yield [<вьшаваемый элемент последовательности>=Nоnе] В отличие от оператора возврата тановкой return (см. разд. 6.2.1), оператор возврата с приос­ yield не завершает выполнение функции, а всего лишь приостанавливает его. При следующем вызове этой функции ее выполнение возобновится с того мес­ та, на котором было приостановлено. Завершение выполнения генератора-функции служит сигналом окончания форми­ руемой им последовательности. Пример простейшего генератора-функции, при каждом вызове выдающего назва­ ние очередного языка программирования: >>> def languages(): yield 'Python' yield 'JavaScript' yield 'Java' yield 'RuЬy' Проверим его в действии: >>> for 1 in languages(): print(l, end=', ') Python, JavaScript, Java, Ruby, Более сложный генератор-функция, принимающий конечное, начальное значения, шаг диапазона, показатель степени и выдающий последовательность, содержащую кортежи из двух элементов: очередного целого числа из заданного диапазона и его степени с заданным в параметре показателем: >>> def powers(end, power, start=l, step=l): for r in range(start, end + 1, step): yield r, r ** power
Часть 160 >>> for р 11. Язык Python: расширенные инструменты in powers(l0, 4): print(p, end=', ') (1, 1), (2, 16), (3, 81), (4, 256), (5, 625), (6, 1296), (7, 2401), (8, 4096), (9, 6561), (10, 10000), >>> for р in powers(20, 4, start=ll, step=3): print(p, end=', ') (11, 14641), (14, 38416), Напишем программу (листинг во простых чисел, начиная с (17, 83521), (20, 160000), 10.1), выдающую заданное пользователем количест­ 1. Для формирования последовательности простых чисел объявим генератор-функцию. from math import sqrt, floor def simple_numЬers(count): for i in range(count + 1): if i % 2 != О: flag = True for j in range(3, floor(sqrt(i)) + 1, 2): if i % j == О: flag = False break if flag: yield i рrint('Генерирование простых чисел') count = int(input('Bвeдитe требуемое for numЬer in simple_numЬers(count): print(numЬer, end=', ') print () input('Для количество простых чисел: завершения программы нажмите ')) <Enter>') Простым считается нечетное число, которое не делится без остатка на все числа от 3 до квадратного корня этого числа, округленного до ближайшего меньшего це­ лого. Вычисление простых чисел мы производим в генераторе-функции simple_ numЬers () . Для округления числа до ближайшего меньшего целого используем функцию floor() из модуляmаth(см.разд. 4.1.3.2). 10.2. Dunder-мeтoды и их использование Dunder-мemoд - метод класса, вызываемый самой исполняющей средой в опреде­ ленные моменты времени существования объекта этого класса. Имя такого метода начинается и заканчивается двойным символом подчеркивания 1 Собственно, вание). слово «dunder» в названии - U 1. это сокращение от англ. «douЫe underline» (двойное подчерки­
Урок 10. Функции и классы особой функциональности 161 В частности, к dunder-мeтoдaм относятся конструктор и деструктор, знакомые нам по разд. 7.2.2. Объявив в классе тот или иной dunder-мeтoд, можно реализовать преобразование объекта этого класса в строку или число, поддержку арифметических операций с этим сравнение объектом, объектов, наделить объекты функциональностью последовательности или отображения и пр. Преобразование объектов 10.2.1. в другие типы данных Чтобы объект какого-либо класса мог преобразовываться в другой тип данных, надо объявить в классе следующие dunder-мeтoды: вызывается при преобразовании текущего объекта в строку. ♦ _str_ (self) - Должен возвращать строку; ♦ _ int_ вызывается при преобразовании текущего объекта в целое чис­ ( sel f) - ло. Должен возвращать целое число; ♦ _ floa t _ вызывается при преобразовании текущего объекта в вещест­ ( self) - венное число. Должен возвращать вещественное число; ♦ _ bool _ вызывается при преобразовании текущего объекта в логиче­ ( self) - скую величину. Должен возвращать логическую величину; ♦ _ bytes _ ( self) - вызывается при преобразовании текущего объекта в после­ довательность байтов типа bytes (см.разд. 4.4). Должен возвращать объект типа bytes; ♦ _ n J) - round_ ( self [, цией round() (см.разд. вызывается при округлении текущего объекта функ­ 4.1.3.1). Вторым параметром передается требуемое коли­ чество цифр после точки. Если вторым параметром передано число О, метод должен возвращать целое число, в противном случае ♦ _floor_(self) - вещественное число; вызывается при округлении текущего объекта функцией floor () из модуля rnath (см. разд. ♦ - 4.1.3.2). Должен возвращать целое число; _ вызывается при округлении текущего объекта функцией ceil _ ( self) ceil () из модуля rnath. Должен возвращать целое число; ♦ _trunc_(self) - вызывается при округлении текущего объекта функцией trunc () из модуля rnath. Должен возвращать целое число; ♦ _hash_(self) - вызывается при попытке получить хеш текущего объекта вы- зовом встроенной функции hash ( ) . Хеш - число, однозначно идентифицирующее заданное значение. Хеш текущего объекта необходимо вычислять на основе какого-либо значения, не изменяющегося в процессе функционирования объекта. Для вычисления хеша на основе заданного значения применяется встроенная функция hash (<значение>) .
Часть 162 Если в классе объявлен dunder-мeтoд 11. _ Язык Python: расширенные инструменты hash_ (), объекты этого класса могут быть использованы в качестве ключей у элементов словарей. Пример класса Rectangle, представляющего прямоугольник, в котором объявлены некоторые из упомянутых dunder-мeтoдoв: >>> from random import random >>> class Rectangle: def init (self, width, height): self.width = width self.height = height # Не изменяющееся псевдослучайное # def def def def def def вычисляться число, по которому будет хеш объекта self. id = random () str (self): return f'{self.width) \xd7 {self.height)' float (self): return float(self.width * self.height) int (self): return round(self.width * self.height) round (self, n=0): if n == О: return self. int () else: return round(self. float (), n) bool (self): return self. int () > О hash (self): return hash(self. id) >>> r = Rectangle(l00, 200) >>> str(r), int(r), float(r), round(r, 3), bool(r), hash(r) ('100 х 200', 20000, 20000.0, 20000.0, True, 1404704229394299392) 10.2.2. Поддержка арифметических операций с объектами. Перегрузка операторов Перегрузка операторов 11 изменение функциональности операторов Python приме­ нительно к определенному классу. Перегрузив в классе, например, операторы сложения и вычитания, можно будет складывать объекты этого класса с числами, вычитать числа из объектов и объекты из чисел. Перегрузка операторов выполняется объявлением в классе dunder-мeтoдoв, приве­ денных в табл. 10.1. Все эти методы должны возвращать результат: элементарное значение, новый объект того же класса или ссьmку на текущий объект мер далее в этом разделе. - см. при­
Урок 10. Функции и классы особой функциональности Таблица 10.1. Dипdеr-методы, реализующие перегрузку арифметических операторов Оператор 1 Метод о + n о. о += n о. n - - add- (self, n) о *= n о. - irnul - (self, n) о //= n о. n %о о. о ** n о. о **= n о. abs (о) о. n о. -= n - rnul - (self, n) о. о. о о. // n о - (self, n) rsuЬ * n о n + - о о. Метод о о. о Оператор - iadd- (self, n) о n / 163 - - rtruediv- (self, n) - floordiv- (self, n) - ifloordiv- (self,n) - rrnod- (self, n) _ pow_ (self, n) - ipow_ (self, n) - abs - о. - radd- (self, n) - (self, n) suЬ - (self, n) isuЬ о о. - rrnul - (self, n) о / n о. - truediv- (self, n) о /= n о. n * n // о о. о %n о. о %= n о. n ** -о о о. о. - itruediv- (self, n) - rfloordiv- (self,n) - rnod- - irnod- - rpow_ (self, n) - neg- (self, n) (self, n) (self) (self) Пример класса тernperature, в котором реализована перегрузка операторов сложе­ ния - обычного и комбинированного: >>> class Ternperature: init (self, value): def self.value = value add (self, n): def # Если объект складывается с числом, возвращаем новый # того же класса, содержащий сумму return self. class (self.value + n) radd ( self, n) : def # Если число складывается с объектом, # возвращаем сумму в виде числа return self.value + n iadd (self, n): def # Если выполняется комбинированное сложение, изменяем # текущий объект и возвращаем ссылку на него self.value += n return self объект >>> t = Temperature (20) >>> t2 t + 20; t2.value 40 >>> 30 + t 50 1 Здесь и далее о - объект класса, в котором объявлен соответствующий dunder-мeтoд, а n - число.
Часть 164 11. Язык Python: расширенные инструменты >>> t2 += 40; t2.value 80 10.2.3. Подд.ержка операций сравнения Для перегрузки операторов сравнения достаточно объявить в классе dunder-мeтoды, приведенные в табл. 10.2. Эти методы должны возвращать результат в виде логиче­ ской величины. Таблица n о. о < n о. о <= n о. != n о. о > n о. о >= n о. _eq_ (self, n) о (self, n) - le - (self, n) - l t- Метод Оператор Метод Оператор о== 10.2. Dипdег-методы, реализующие перегрузку операторов сравнения - ne- (self, n) - gt - (self, n) - ge- (self, n) Пример производного от класса Temperature класса Temperature2, оснащенного под­ держкой операторов сравнения == и ! =: >>> class Temperature2(Temperature): def _eq_(self, n): return self.value == n ne (self, n): def return self.value != n >>> tl = Temperature2(20); t2 = Temperature2(40); >>> tl == tЗ, tl == t2 (True, False) >>> tl != tЗ, tl != t2 (False, True) 10.2.4. tЗ Temperature2(20) Создание своих итераторов Любой класс можно оснастить функциональностью итератора (см.разд. 5. 9) тогда его любой объект при последовательных обращениях будет выдавать один за другим элементы заданной последовательности. Для этого надо объявить в классе следующие dunder-мeтoды: ♦ _ i ter_ (self) - вызывается единственный раз перед извлечением из итератора первого элемента заданной последовательности. Должен выполнять необходи­ мые предварительные действия и возвращать ссылку на текущий объект. Наличие в классе этого метода сообщает исполняющей среде, что класс является итератором; ♦ _next_(self) - вызывается при извлечении очередного элемента последова­ тельности. Должен возвращать элемент, а если последовательность закончи­ лась - возбуждать исключение класса Stopiteration;
Урок ♦ 10. _ Функции и классы особой функциональности len_ ( sel f) - 165 должен возвращать размер заданной последовательности. Ис­ пользуется функцией len () (см. разд. 4.2.3.1). В принципе, объявлять этот метод необязательно - класс-итератор будет рабо­ тать и без него, но узнать размер последовательности не получится; ♦ _reversed_ (self) - должен возвращать новый объект этого же класса, содер­ жащий «перевернутую» последовательность из текущего объекта. Используется функцией reversed () (см.разд. Напишем программу (листинг 5. 9). 10.2), Объявлять этот метод также необязательно. которая будет запрашивать у пользователя произвольную строку и выводить ее задом наперед. Дr~я реализации этого объявим класс ReversedString, имеющий функциональность итератора, поочередно выдаю­ щего символы заданной строки, начиная с конца. print ('Вывод строки задом наперед') class Reversed_String: def init (self, s): self. s = s def iter (self): # Задаем индекс текущего символа равным О self. i = О return self def next (self): if self. i > len(self._s) - l: # Если строка кончилась, возбуждаем исключение raise Stopiteration else: # В противном случае возвращаем очередной символ # и увеличиваем индекс текущего символа на l а= self. s[-self. i - 1) self. i += 1 def def return а len (self): return len(self. s) reversed (self): return self. class (self. s[::-1)) source string = inрut('Введите строку: rs = ReversedString(source string) ') print ('Длина исходной строки: ', len ( rs) ) pr int ( 'Перевернутая строка: ' , end=' ' ) for ch in rs: print(ch, end=' ') print () input('Для завершения программы нажмите <Enter>')
Часть 166 Вот пример вывода программы (строка, 11. Язык Python: расширенные инструменты введенная пользователем, выделена полу­ жирным шрифтом): Вывод строки Введите Длина исходной Перевернутая Для задом наперед строку: строка: завершения 10.2.5. язых программирования 28 nohtyp python строки: яинавориммаргорn программы нажмите кызя <Enter> Создание своих последовательностей Любой класс можно оснастить функциональностью пронумерованной последова­ тельности - тогда к значениям, хранящимся в его объектах, можно будет обра­ щаться по индексам. Дnя этого достаточно объявить в классе следующие dunder- мeтoды: ♦ _geti tem_ (sel f, <индекс>) - вызывается при извлечении элемента с задан­ ным индексом. Должен возвращать этот элемент; ♦ _setitem_(self, <индекс>, <значение>) - вызывается при присваивании ука­ занного значения элементу с заданным индексом. Не должен возвращать резуль­ тата; ♦ _ deli tem_ (self, <индекс>) - вызывается при удалении элемента с заданным индексом. Не должен возвращать результата. Все эти методы предварительно должны проверять тип полученного индекса является ли он целым числом (тип int) или срезом (тип slice). Если был полу­ чен индекс неподходящего типа, они должны возбуждать исключение класса TypeError, а если был получен несуществующий индекс - класса IndexError. Если же операция, реализуемая соответствующим методом (например, удаление элементов), не должна поддерживаться классом, то в теле этого метода следует возбудить исключение класса NotimplementedError; ♦ _contains_ (self, <значение>) - должен возвращать True, если в последова­ тельности содержится заданное значение, и пользуется операторами in и False - в противном случае. Ис­ not in. Обычные строки типа str являются неизменяемыми. Напишем класс мutаЫе String, представляющий строку, которую можно изменять так же, как и список: >>> class MutaЫe String: def init (self, value): self._s = list(value) # Вспомогательный метод, # проверяющий корректность заданного индекса def check_index(self, ind): if type(ind) == int or type(ind) == slice: if type(ind) == int and ind > len(self. s) - 1: raise IndexError(f'Heдonycтимый индекс: {ind}')
Урок 1О. Функции и классы особой функциональности def def def def def else: raise ТуреЕrrоr(f'Недопустимый _getitem_(self, ind): self._check_index(ind) return self. s[ind] setitem_(self, ind, value): self._check_index(ind) self. s[ind] = value delitem_(self, ind): self._check_index(ind) del self._s[ind] contains (self, value): return value in self. s str (self): return '' .join(self._s} 167 тип индекса: (type(ind) }') >>> s = MutaЫe_String('Python'} »> s[O], s[-1] ( 'Р', 'n'} >>> s[O] = 'J' »> print(s) Jython >>> del s[2:4] »> print(s) Jyon >>> 'J' in s, 'Р' in s (True, False) 10.2.6. Создание своих отображений Любой класс также можно наделить функциональностью отображения, объявив в нем описанные в разд. 10.2.5 dunder-мeтoды _getitem_ (), _setitem_ (), _ deli tem_ () и _ contains _ (). Следует помнить, что вместо целочисленных ин­ дексов этим методам будут передаваться ключи, скорее всего, строковые, и при об­ ращении по несуществующему ключу необходимо возбуждать исключение класса KeyError. Напишем класс Version, который будет хранить номер версии какого-либо про­ граммного продукта, разбитый на части: старшая версия, младшая версия и моди­ фикация. Доступ к частям версии будет осуществляться по строковым ключам major, minor и sub соответственно. Операцию удаления элементов заблокируем, возбуждая в теле метода_deli tem_ () исключение NotimplementedError: >>> class Version: def init (self, major, minor, sub=O): self._major = major self. minor = minor self. suЬ sub
Часть 168 11. Язык Python: расширенные инструменты def _getitern_(self, k): match k: case 'major': return self._major case 'rninor' : return self. rninor case 'suЬ': return self. suЬ case raise KeyError ( f 'Недопустимый ключ: {k 1' ) def setitern (self, k, v): match k: case 'major': self._rnajor v case 'rninor': self. rninor = v case 'suЬ': self. suЬ = v case raise КеуЕrrоr(f'Недопустимый ключ: {kl') def delitern (self, k): raise NotirnplernentedError def contains (self, v): return v == 'rnajor' or v == 'rninor' or v == 'sub' def str (self): return f'{self._majorf.{self._rninorf. {self. suЬI' >>> v = Version(3, 13, 1) >>> v['rnajor'], v['rninor'], (3, 13, 1) >>>v['sub'] 5 >>> print(v) 3.13.5 10.2.7. ♦ v['suЬ'] Прочие dunder-мeтoды _ call _ () - вызывается при вызове текущего объекта, подобном вызову функ­ ции. Все заданные при вызове объекта параметры будут переданы этому методу. Формат объявления метода: call (self[, <параметр 1>, <параметр Пример: >>> class Clsl: def init (self, greating): self.greating = greating 2>, . . . , <параметр N>])
Урок 1О. Функции и классы особой функциональности def (self, name): call return f'{self.greating), 169 {name)!' >>> obj = Сlsl('Здравствуйте') >>> obj ('Владимир') 'Здравствуйте, Владимир! ' ♦ getattr (self, <имя атрибута>) - вызывается при попытке получить зна­ чение несуществующего атрибута с заданным именем. Возвращенный методом результат станет значением этого атрибута; ♦ setattr_ (self, <имя атрибута>, <значение>) - вызывается при присваива­ нии заданного значения атрибуту с указанным именем. Если в теле метода getattr () или setattr () потребуется обратиться к какому-либо атрибуту текущего объекта, необходимо обращаться к элементу словаря из атрибута _ ctict _, ключ которого совпадает с именем требуемого ат­ рибута. Обращение непосредственно к атрибуту вызовет цепочку рекурсивных вызовов того же метода, что приведет к ошибке типа RecursionError и аварий­ ному останову программы. Атрибут _dict_ создается исполняющей средой в любом объекте любого клас­ са и хранит значения всех атрибутов, содержащихся в текущем объекте. При об­ ращении к значениям атрибутов через этот словарь методы _getattr_ () или _ setattr _ () (а также описываемый далее _getattribute_ ()) не вызываются. Вот пример класса Version2, аналогичного классу из разд. 10.2.6, в котором но­ мер версии хранится в словаре из атрибута_version, а обращение к отдельным частям номера версии осуществляется через реально несуществующие атрибуты major, minor И sub: >>> class Version2: names = ('major', 'minor', 'suЬ') (self, major, minor, sub=O): init def # Обращаемся не к атрибуту version, а к элементу dict # с ключом version словаря из атрибута self. dict ['_version'] = {'major': major, 'minor': minor, 'suЬ': (self, attrname): def _getattr if attrname not in self. class .names: raise AttributeError(f'Heдoпycтимый атрибут: ' f' {attrname)') return self. dict_['_version'] [attrname] setattr (self, attrname, value): def if attrname not in self. class .names: raise AttributeError(f'Heдoпycтимый атрибут: ' f' {attrnamel ') self. dict ['_version'] [attrname] = value suЬ)
Часть 170 11. Язык Python: расширенные инструменты >>> obj = Version2(3, 13) >>> obj.major, obj.minor, obj.sub (3, 13, О) >>> obj.minor = 14 >>> obj.major, obj.minor, obj.sub ( 3, 14, О) ♦ getattribute (self, <имя атрибута>) - аналогичен getattr (), только вызывается при попытке получить значение любого атрибута текущего объекта, даже существующего. Если в теле этого метода необходимо получить значение какого-либо атрибута, следует вызвать метод _getattribute_ ственно к атрибуту вызовет () суперкласса. Обращение непосред­ цепочку рекурсивных вызовов метода _getattribute_ () текущего класса, что приведет к ошибке типа RecursionError и «падению» программы. Пример: >>> class Cls2: def _getattribute_(self, attrname): # Выводим сообщение об обращении к dict # за исключением if attrname != ' dict '· любому атрибуту, рrint(f'Получение значения атрибута {attrname}') return super() ._getattribute_(attrname) def setattr (self, attrname, value): рrint(f'Присваивание атрибуту {attrname} ' f'значения {value}') self. dict [attrname] = value >>> obj = C1s2() >>> obj.attr = 123 Присваивание атрибуту attr значения 123 >>> obj.attr Получение значения атрибута attr 123 ♦ delattr (self, <имя атрибута>) - вызывается при удалении атрибута с за­ данным именем. 10.3. Написание менеджеров контекста Собственные менеджеры контекста (см.разд. 9.2) можно реализовать в виде как функций, так и классов. 10.3.1. Функции-менеджеры контекста Функция, выполняющая роль менеджера контекста, должна быть декорирована декоратором contextmanager () из модуля contextlib.
Урок 1О. Функции и классы особой функциональности 171 В теле этой функции работа с контекстом должна выполняться в выражении обра­ ботчика исключений (см.разд. 9.1.2): ♦ создание, настройка и выдача контекста - в блоке try, с применением операто- ра возврата с приостановкой yield (см.разд. ♦ завершающие действия с контекстом 10.1); в блоке finally. - Для примера напишем функцию-обработчик контекста avg ( 1, которая создает и вы­ дает пустой список, позволяет добавлять в него числа, а в заключение выводит в консоли среднее арифметическое чисел из списка: >>> frorn contextlib irnport contextrnanager >>> @contextrnanager def avg ( 1: # Создаем пустой список 1st = [] try: # Вьщаем список yield 1st finally: # Рассчитываем и выводим среднее арифметическое print (surn(lst) / len (1st)) >>> with avg() as 1: 1 += [1, 2] l.append(З) 2.0 В виде функций реализуются менеджеры контекста простые, не имеющие какой бы то ни было дополнительной функциональности (наподобие написанного нами). 10.3.2. Классы-менеджеры контекста Чтобы превратить класс в менеджер контекста, достаточно объявить в нем два dunder-мeтoдa: ♦ enter (selfl - вызывается в начале выполнения обработчика контекста. Должен создавать, настраивать и возвращать оператором return контекст; ♦ _exit_(self, exc_type, exc_value, traceback) - вызывается либо по завер­ шении выполнения обработчика контекста, либо после возникновения исключе­ ния в его теле. Если исключение не возникло, последние три параметра получат значение None. Если исключение возникло, с параметром ехс _ t уре будет передана ссылка на класс исключения, с параметром ехс _ value а с параметром traceback должен вернуть значение ссылка на сам объект исключения, ссылка на объект со стеком вызовов. Тогда метод тrue, если он обработал возникшее исключение,
Часть 172 11. Язык Python: расширенные инструменты и False, если он его не обработал (тогда исключение будет передано вышестоя­ щему обработчику). Напишем (см. разд. класс-обработчик контекста Avg, аналогичный функции avg () 10.3.1): >>> class Avg: def enter (self): self. lst = [] # В качестве контекста возвращаем текущий объект return self # Объявляем метод для добавления числа def add(self, num): self.lst.append(num) def exit (self, exc_type, exc_value, traceback): if ехс value: # Обрабатываем возникшее исключение print(exc_value) return True else: print(sum(self.lst) / len(self.lst)) >>> with Avg() as 1: l.add(l) l.lst += [2, 3] 2.0 В виде классов реализуют менеджеры контекста с какой-либо дополнительной функциональностью (например, содержащие дополнительные методы или способ­ ные работать вне выражений обработчиков контекста как обычные классы). 10.4. Перечисления Перечисление - класс с классовыми атрибутами, хранящими произвольные неизме­ няемые значения. Элемент - классовый атрибут, принадлежащий перечислению. Попытка присвоить элементу перечисления другое значение приведет к ошибке типа AttributeError. По существующим соглашениям имена у элементов перечислений набирают пол­ ностью в верхнем регистре. Класс перечисления должен быть производным от одного из следующих классов, объявленных в модуле enum: ♦ Enum - перечисления, элементы которых могут хранить значения произвольных типов, в том числе и повторяющиеся.
Урок 10. Функции и классы особой функциональности 173 Перечисление, элементы которого хранят строки: >>> from enum import Enum >>> class Frameworks(Enum): LARAVEL = 'Laravel' DJANGO = 'Django' EXPRESS = 'Express' RAILS = 'Ruby on Rails' RUBY_ON_RAILS = 'Ruby on Rails' >>> Frameworks.LARAVEL <Frameworks.LARAVEL: 'Laravel'> Еще одно перечисление, элементы которого хранят целые числа: >>> class Colors(Enum): RED = 1 2 GREEN BLUE = 3 = RED + GREEN + BLUE WНITE >>> Colors.RED, Colors.WHITE (<Colors.RED: 1>, <Colors.WHITE: 6>) Если конкретные целочисленные значения элементов не важны, для их создания можно использовать функцию auto () из модуля enum. Она возвращает последо­ вательно увеличивающиеся целые числа, начиная с 1: >>> from enum import auto >>> class Colors2(Enum): RED = auto() GREEN = auto () BLUE WНITE = auto() = auto () >>> Colors2.RED, Colors2.WHITE (<Colors2.RED: 1>, <Colors2.WHITE: 4>) Следует помнить, что использовать значения элементов таких перечислений в операциях совместно со значениями других типов нельзя - это к ошибке типа TypeError: >>> Frameworks.Django + ' 5.0' TypeError: unsupported operand type(s) for +: ~ 'Frameworks' and 'str' >>> Colors.WHITE / 2 TypeError: unsupported operand type(s) for /: 'Colors' and 'int' приведет
Часть 174 11. Язык Python: расширенные инструменты Если сравнить элемент такого перечисления с его значением, результатом всегда будет False: >>> Frameworks.DJANGO == 'Django' False ♦ StrEnurn - перечисления с элементами, хранящими исключительно строки: >>> from enurn import StrEnurn >>> class Frameworks2(StrEnurn): LARAVEL = 'Laravel' DJANGO = 'Django' EXPRESS = 'Express' RAILS = 'Ruby on Rails' RUBY_ON_RAILS = 'Ruby on Rails' Попытка дать элементу такого перечисления значение, отличное от строки, при­ ведет к ошибке типа TypeError. Элементы способны преобразовываться в обычные строки, вследствие чего мо­ гут быть использованы в операциях со строками: >>> Frameworks2.DJANGO + ' 5.0' 'Django 5.0' >>> Frameworks2. LARAVEL. find ( 'v') 4 Допустимо сравнивать элемент с его значением: >>> Frameworks2.DJANGO == 'Django', Frameworks2.DJANGO (True, False) ♦ IntEnurn - 'Express' перечисления, элементы которых хранят только целые числа: >>> from enurn import IntEnurn >>> class ColorsЗ(IntEnurn): RED = 1 GREEN = 2 ВШЕ= WHITE >>> 3 = RED ColorsЗ.RED, + GREEN + BLUE ColorsЗ.WHITE (<ColorsЗ.RED: 1>, <ColorsЗ.WHITE: 6>) Попытка дать элементу такого перечисления значение, отличное от целого чис­ ла, приведет к ошибке типа TypeError. Для занесения значений в элементы можно использовать описанную ранее функцию auto () ИЗ модуля enurn. Элементы способны преобразовываться в целые числа, поэтому их можно ис­ пользовать в операциях с числами: >>> ColorsЗ.BLUE 1.5 / 2
Урок Функции и классы особой функциональности 1О. 175 Допустимо сравнивать элемент с его значением: == 1, Colors3.BLUE == 200 (True, False) >>> ColorsЗ.RED Значения элементов перечислений можно использовать для указания каких-либо режимов выполнения функций или методов. Вот пример вызова функции с переда­ чей ей элементов объявленных ранее перечислений: framework=Frameworks.DJANGO, main_color_scheme=Colors2.GREEN) make_me_cool_site(name='Moй крутой сайт', Важным преимуществом перечислений является то, что значения их элементов не­ возможно изменить - случайно или намеренно, - тем самым нарушив работу программы. 10.5. Что еще нужно знать о классах особой функциональности ♦ 1О. 2 и 1О. 3. 2. В классе можно объявить любые dunder-мeтoды, описанные в разд. Например, класс может одновременно поддерживать арифметические операции, являться последовательностью и отображением. ♦ Над элементами любого перечисления можно производить следующие действия: • обращение к элементу: □ в стиле словарей, используя строку с именем элемента в качестве ключа: >>> Frameworks [ 'EXPRESS'] <Frameworks.EXPRESS: 'Express'> □ в стиле вызова функций, записав значение элемента в круглых скобках: >>> Frameworks ( 'Laravel') <Frameworks.LARAVEL: 'Laravel'> • получение имени и значения элемента из его атрибутов name и value соответ­ ственно: >>> Frameworks.RAILS.name, Frameworks.RAILS.value ('RAILS', 'RuЬy on Rails') • перечисление проверка на вхождение и невхождение заданного элемента в с помощью операторов in и not in: >>> Frameworks.DJANGO in Frameworks, Colors.RED in Frameworks (True, False) ♦ Любое перечисление поддерживает функциональность итератора, поэтому его элементы можно перебрать в цикле: >>> for el in Colors: print(f'(el.namef: (el.valuef', end=' RED: 1 1 GREEN: 2 1 BLUE: 3 1 WHITE: 6 1 1 ')
176 ♦ Часть 11. Язык Python: расширенные Если декорировать перечисление декоратором global _ enum () инструменты из модуля enшn, к элементам этого перечисления можно будет обращаться, просто указывая их имена, без ссылки на сам класс перечисления (глобш,ьное перечисление): >>> from enum import global_enum >>>#Декорируем перечисление декоратором global_enum() ... >>> @global_enum class FrameworksЗ(Enum): LARAVEL = 'Laravel' DJANGO = 'Django' EXPRESS = 'Express' RAILS = 'Ruby оп Rails' >>> # ... и получаем возможность >>>#без указания класса >>> DJANGO main 10.6. 1. обращаться к его элементам перечисления .DJANGO Самостоятельные упражнения Напишите программу, которая будет выдавать заданное пользователем коли­ чество чисел из ряда Фибоначчи, начиная с 1. Используйте для формирования последовательности чисел генератор-функцию. Ряд Ф~боначчи начинается с чисел О и 1, а каждое последующее число является 11 2. суммои двух предыдущих. Напишите программу, выводящую заданную строку задом наперед и исполь­ зующую функциональность последовательности, а не итератора (как аналогич­ ная программа из листинга 3. 10.2). Напишите очередную версию программы-преобразователя величин температу­ ры из одной шкалы в другую гах 7.1 и 7.2), ( код предыдущих версий представлен в листин­ в которой для идентификации температурной шкалы используют­ ся не строки, а элементы перечисления.
Урок11 Регулярные выражения Многие программы занимаются обработкой текста: ищут в них фрагменты, совпа­ дающие с заданными шаблонами, переформатируют или заменяют найденные фрагменты другими согласно определенным правилам. Регулярное выра:ж:ение 11 шаблон, применяемый для поиска совпадающих с ним фрагментов текста. 11.1. Создание регулярных выражений Для создания регулярного выражения применяется функция compile () из модуля re: compile (<шаблон регулярного выражения> [, <флаги>=О] ) Шаблон регулярного выражения задается в виде строки обычно с отключенной обра­ боткой специальных символов (см.разд. 4.2.1.1)-так удобнее. Флаги мы рассмот­ рим в разд. 11.2.8. Функция возвращает объект класса Pattern, представляющий готовое к исполь­ зованию регулярное выражение. Следует отметить, что по умолчанию при поиске в обрабатываемом тексте под­ строк, совпавших с регулярным выражением, регистр символов учитывается. Пример 1 : import re re.compile(r'Python') regex regex = re.compile(r'Java') 11.2. # Результат nоиска: !Pythonj !Python!З Java # Результат поиска: Python PythonЗ IJaval Написание регулярных выражений Шаблоны регулярных выражений пишутся на особом языке и представляют собой комбинацию из обычных и специальных символов. Специальные символы регулярных выражений имеют особое значение. Они делят­ ся на несколько разновидностей, описываемых далее. 1 Здесь и далее фрагменты с-q>ок, совпадающие с заданным регулярным выражением, выделены рамками.
178 Часть Язык Python: расширенные инструменты Метасимволы 11.2.1. Метасимвол 11 11. обозначает любой символ, относящийся к какой-либо группе, символ с указанным кодом или определенное место в обрабатываемом тексте. Метасимволы, поддерживаемые Python, приведены в табл. 11.1. Таблицы Мета символ . (точка) Описание \w Пример Любой символ, кроме перевода строки и возврата каретки 11.1. Метасимволы (\n) ~~ l!J [5]19 ffIO@JШ '\w' ~~[!][5][9~ ~ ! (\r) Алфавитно-цифровой символ (буква, цифра Результат ' 1 или символ подчеркивания) \W Не алфавитно-цифровой символ '\W' PythonOз[I] \d Цифра '\d' Python \D Не цифра '\D' ~~[!][5]19~□ 3 ш Пробельный символ (пробел, табуляция, '\s' Python0З ! '\S' ~~l!J[5]19~ ~ш '\u2260' 2 В З '\Ьо' [9т [9боза \s @] ! перевод строки или возврат каретки) \S Не пробельный символ \u<код> Символ с указанным четырехзначным шестнадцатеричным Unicode-кoдoм \Ь Начало или конец слова 'а\Ь' \В Не начало и не конец слова от обоз~ '\Во' от об[9за 'а\В' от обоза лили \А 1 Начало текста '"'о' [9т обоза $ ИЛИ Конец текста 'о$' от обоза 'а$' от обоз~ \Z 1 Примеры: regex re.compile(r'\w\w\w\w\w\w\s\d') regex = re.compile(r'\bo\wo\b') 11.2.2. 11 Поднаборы 11.2 приведен перечень поддерживаемых поднаборов. Разница между этими метасимволами будет описана далее. Java 14 # [ого[ [обо[ [око[ окно огого ага Поднабор - обозначает любой символ из указанного набора. В табл. 1 # [Python З[
Урок 11. 179 Регулярные выражения Таблицы Пример Описание Поднабор Любой из символов, приведенных в скобках [ <символьt>] [ "<символьt>] Любой из символов, не приведенных в скобках ["<cl>-<c2>] Любой ИЗ СИМВОЛОВ не в диапазоне ОТ cl ДО с2 Результат '[otyid]' P~~hi9n '["otyid]' ~yt[61o~ '[a-k]' oВJ~nlя]o '["a-k]' [§j a~g[9 Любой из символов в диапазоне от cl до с2 [ <cl>-<c2>] 11.2. Поднаборы В одной паре квадратных скобок можно указывать несколько поднаборов, записы­ вая их слитно: regex regex Вариант 11.2.3. 11 # 1§[3Ш]n~о # lo]j а n g о О 5 [J l re.compile (r' (A-Ka-k] ') re.compile (r' ["0-9a-z] ') Вариант - <фрагмент обозначает любой фрагмент из перечисленных: l>l<фрагмент 2>1 . . . !<фрагмент N> Примеры: re.compile(r'htmlhtmllshtml') regex re.compile(r'Python\dlJava\d\d') = regex 11.2.4. 11 Квантификаторы Квантификатор - указывает количество повторений фрагмента. Вот квантификаторы, поддерживаемые ♦ #~~~txt # [PythonЗ[ [Javal4[ РНРВ <фрагмент>+ - Python: фрагмент может присутствовать в произвольном количестве эк­ земпляров: re.compile(r'CS+S') regex regex = re.compile(r' [a-z]\d+') ♦ <фрагмент>+? - [cssssssssssss[ # [css[ § # [al23[ jь56784[ ~ abcde cs то же, что и предыдущий, только старается «захватить» как можно меньше экземпляров заданного фрагмента: regex = re.compile(r'CS+?S') ♦ <фрагмент>* - # [css[ [css[s [css[ssssssssss cs фрагмент может присутствовать в произвольном количестве экземпляров или вообще отсутствовать: regex ♦ = re.compile(r'CS*S') <фрагмент>*? - # [css[ [csss[ [cssssssssssss[ § то же, что и предыдущий, только старается «захватить» как можно меньше экземпляров заданного фрагмента: regex = re.compile(r'CS+?S') # §s §ss §sssssssssss §
180 ♦ Часть <фрагмент>? - 11. Язык Python: расширенные инструменты фрагмент может присутствовать в одном экземпляре или отсутст­ вовать: regex = re.compile(r'CS?S') ♦ <фрагмент> { <т>) - # jcssj 1css1s jcssjssssssssss § фрагмент должен присутствовать строго в количестве т эк­ земпляров: regex = re.compile(r'CS{2}S') ♦ <фрагмент> { <m>, ) - # css jcsssj 1csssjsssssssss cs фрагмент должен присутствовать в количестве т или более экземпляров: regex = re.compile(r'CS{2, )S') ♦ <фрагмент> ( <т>, ) ? - # css jcsssl jcssssssssssssl cs то же, что и предыдущий, только старается «захватить» как можно меньше экземпляров заданного фрагмента: regex = re.compile(r'CS{2, }?S') ♦ <фрагмент>{<т>,<п>) - # css jcsssj 1csss1sssssssss cs фрагмент должен присутствовать в количестве от т до п экземпляров: regex = re.compile(r'CS{2,l0}S') ♦ <фрагмент> { <т>, <n>)? - # css jcsssl jcsssssssssssjs cs то же, что и предыдущий, только старается «захватить» как можно меньше экземпляров заданного фрагмента: regex = re.compile(r'CS{2,10}?S') ♦ <фрагмент> { <т>, <n>) + - # css 1csss11csss1sssssssss cs то же, что и предыдущий, только старается «захватить» как можно больше экземпляров заданного фрагмента: regex = re.compile(r'CS{2,10}+S') # css csss cssssssssssss cs В этом примере фрагмент s12, 10)+ шаблона регулярного выражения «захватил» все буквы «S» в подстроке «CSSSSSSSSSSSS», и на долю последующего фраг­ мента s ничего не осталось. В результате ни одна из подстрок заданного текста с регулярным выражением не совпала. Еще примеры: regex re.compile(r'\w{б,10)\s?\d+') # iPython Зi iJavaScript 20221 Java 14 regex = re.compile(r'\w+@\w+.\w{2,3}') # juser@email.ru1 admin.server@com 11.2.5. Утверждения Утверждение - указывает на местоположение фрагмента относительно другого фрагмента, причем последний не включается в состав подстроки, совпавшей с регу­ лярным выражением. Вот доступные в Python утверждения: ♦ <фрагмент> ( ?=<другой фрагмент>) - за фрагментом должен следовать другой фрагмент. Найдем все слова, после которых находится запятая: regex = re.compile(r'\b\w+\b(?=,) ') # jPythonJ, jJavaScriptj, Java РНР
Урок ♦ 11. Регулярные выражения 181 < фрагмент> (? ! <другой фрагмент>) - за фрагментом не должен следовать другой фрагмент. Найдем все слова, после которых нет запятой: regex = re.cornpile(r'\b\w+\b(?!,) ') ♦ (?<=<другой фрагмент>) <фрагмент> - # Python, JavaScript, IJaval IPHPI перед фрагментом должен находиться другой фрагмент. Найдем все отрицательные числа: # 123 regex = re. cornpile ( r' ( ?<=-) \d+') ♦ (?<!<другой фрагмент>) <фрагмент> -@] 46 -1воо11 перед фрагментом не должен находиться другой фрагмент. Найдем все положительные числа: regex = # 11231 -6 re.cornpile(r' (?<!-)\d+') [§1 -воо1 Еще примеры: # Найдем имена переменных в выражениях присваивания # Найдем присваиваемые числа в выражениях = 9000 присваивания regex = re.cornpile (r' (?<==\s) \d+') # аЬс = 11231 Группы 11.2.6. 11 0 # labcl = 123 regex = re. cornpile ( r' [ \w\d] + ( ?=\s=) ') Группа - независимый фрагмент регулярного выражения. Для создания групп применяются следующие языковые конструкции: ♦ (<фрагмент>) -нумерованная группа. Пример регулярного выражения, совпадающего с подстрокой, которая содержит не менее одной цифры, произвольное количество пробельных символов и фраг­ мент «руб». В регулярном выражении присутствует группа, совпадающая с цифрами, которые находятся в начале совпавшей подстроки': regex = re.cornpile(r' (\d+)\s*pyб') # ~ it•i•i•j оубl 1 коп Подстрока обрабатываемого текста, совпавшая с заданным фрагментом, сохраня­ ется в оперативной памяти. Сохраненную подстроку можно вставить в регуляр­ ное выражение, чтобы найти ее следующее вхождение в обрабатываемом тексте. Обратная ссылка - ссьmка на подстроку, совпавшую с заданной группой, которая ставится в шаблоне регулярного выражения. Обратная ссылка на нумерованную группу записывается в формате: \ <порядковый номер rруппы> Нумерация групп начинается с 1. Пример регулярного выражения, совпадающего с любым парным тегом. В регу­ лярном выражении созданы две группы: одна совпадает с именем тега, вторая - с содержимым тега. В записи закрывающего тега применена обратная ссылка на 1 Здесь и далее подстроки, совпавшие с группами, выделяются черной заливкой.
Часть 182 Язык 11. Python: расширенные инструменты первую группу, в которой хранится имя тега. Чтобы поместить в шаблон регу­ лярного выражения символ прямого слеша ( он применяется в закрывающем теге), мы предварили его обратным слешем: regex = re.compile(r'<(\w+)>(.*?)<\/\1>') (?Р< ♦ <имя rруппы> ><фрагмент>) - # 1<$1%•\•\•:¾JS@< / strong>I именованная группа. Аналогичная нумеро­ ванной, только обратная ссылка на нее записывается следующим образом: (?Р=<имя именованной rруппы>) Примеры регулярных выражений, аналогичных приведенным ранее: regex re.compile(r' (?P<sum>\d+)\s*pyб') regex = re.compile(r'<(?P<tname>\w+)>(?P<content>.*?)<\/(?P=tname)>') Какую из двух приведенных разновидностей групп использовать ♦ (?:<фрагмент>) - - дело вкуса\ незахватывающая группа. Совпавшая с ней подстрока не со­ храняется в памяти. Пример регулярного выражения с незахватывающей груп­ пой, которая совпадает с содержимым тега: regex = re.compile(r'<(\w+)>(?:.*?)<\/\1>') Незахватывающие группы применяются в случае, если совпадающие с ними подстроки нигде не используются. Применение таких групп позволяет немного сэкономить оперативную память. 11.2.7. Обычные символы Все прочие символы в шаблонах регулярных выражений обозначают сами себя. Ранее мы уже убедились в этом. Если требуется включить в шаблон регулярного выражения обычный символ, сов­ падающий со специальным символом, его следует предварить обратным слешем (\). Вот пара примеров: # Точка в шаблоне регулярного выражения обозначает любой символ regex = re.compile(r'bhv.ru') # ~hv.rul ~hv:rul ~hv rul # А точка, предваренная обратным слешем, обозначает точку regex = re. compi le ( r' bhv\. ru') # ~hv. ruJ bhv: ru bhv ru 11.2.8. Флаги bhvru Флаги передаются (см. разд. bhvru 11.1 ), функции создающей compile (), регулярное выражение вторым параметром и управляют работой этого регулярного выра­ жения. Флаги задаются элементами глобального перечисления RegexFlag из модуля re или комбинацией этих элементов через символ 1 1: Автор предпочитает нумерованные группы, считая именованные слишком громоздкими.
Урок 11. Регулярные выражения ♦ или IGNORECASE - r s регистр символов не учитывается: re.compile (r'Python' ) re.compile(r'Python', re.I) regex regex ♦ метасимвол или DOTALL - и возвратом каретки # IPythonl [Pythoф python # [Pythonl IPythoф pythod (точка) совпадает также с переводом строки (\n) (\r): # IPythonl \nJavaScript \nJava re.compile(r' .+') re.compile(r' .+', re.S) regex regex # IPyt hon \nJavaScript \nJaval метасимвол л обозначает начало каждой подстроки в строке, м или MULTILINE - ♦ 183 конец каждой под­ разбитой на подстроки символами \n, а метасимвол $ строки: regex regex # Python\nJavaScript\nJava re.compile(r'л.+$') re.compile(r'л.+$', 11.2.9. re.M) # IPythonl\фavaScripффaval Тестирование регулярных выражений Для тестирования регулярных выражений можно использовать интернет-службу Regular Expressions 1О 11 (рис. 11.1 ). Предварительно Python. в списке Flavor (Особенность) слева следует Шаблон регулярного выражения заносится в поле ввода ■ ,w,gн:10,:Ьuild.ttst,юd~ )( + ,... EXPLANATION S.ve new •еае• ctrl+s f " 11 cs .., r•• С S '' & С matches the character С w,[h index $ ТЕSТ SТRING FLAVOR о PCRE2 (РНР >•7.3) Ф PCRE (РНР <7 .3) Ф ECMAScnpt UiVaSc:npt) Ф Golang Ф Java8 67 1, (43 16 or 1ез 8 ) l1terally (caSt: sen~tive) CSS • CSSS CSSSSSSSSSSSS CS с/► </> Python пункт о SAVE&SНARE ■ выбрать Regular expression .., S matches the character s wнh lndex МАТСН ,./ INFORMAnON Match 1 е-з css Hatch 2 4~8 csss t!з ►ifiйiHIЧЫIHF QUICK REFERENCE The'rt! ,re currмdy no sponsors в«оmе ~ sponsor todc'llyl As. .. l lf you're runnlng an ad Ыocl<er, conslder whitelisting regex101 to а AIITokens • Common Tok~ns support tht-WIOSite . Read more. 1:26 Рис . 1 https://regex I О l .com/. 11.1. Интернет-служба Regular Expressions 101 А. .. v rаьс ( 11 аьс 1 Ас ... (а z] (..,а rJ А.. ..
Часть 184 11. Язык (Регулярное выражение), а обрабатываемый текст string Python: расширенные - инструменты в область редактирования Test (Tecтori;tя строка). Совпавшие подстроки в обрабатываемом тексте подсвечи­ ваются. Щелкнув на флагах, находящихся в правой части поля ввода Regular expression, можно вызвать меню для указания этих флагов. А при щелчке на расположенной еще правее кнопке (51 Сору to clipboard (Скопировать в буфер обмена) набранный шаблон будет скопирован в буфер обмена. Использование регулярных выражений 11.3. 11.3.1. Поиск первого совпадения Для поиска в заданном тексте первой (или единственной) подстроки, совпадающей с регулярным выражением, применяются следующие методы класса ♦ match (<текст> [, <начало> [, <конец>] ] ) - Pattern: проверяет, совпадает ли начало за­ данного текста с текущим регулярным выражением. Если совпадение найдено, возвращает объект класса мatch со сведениями о совпадении, в противном слу­ чае - значение None: >>> import re >>> regex = re.compile(r'Python') >>> regex .match ( 'Python - язык программирования') <re.Match object; span=(O, 6), match='Python'> >>> print(regex.match('Язык программирования Python')) None >>> print(regex.match('JavaScript - язык программирования')) None Если заданы начало и конец, из указанного текста извлекается срез, и поиск про­ изводится в нем: >>> ♦ regex.match('Язык программирования Python', 22, 30) <re.Match object; span=(22, 28), match='Python'> search () - проверяет, присутствует ли где-либо в заданном тексте фрагмент, совпавший с текущим регулярным выражением. Формат вызова и возвращае­ мый результат такие же, как и у метода >>> ♦ match (): regex.search('Язык программирования Python') <re.Match object; span=(22, 28), match='Python'> fullmatch () - сравнивает весь заданный текст с текущим регулярным выраже­ нием. Формат вызова и возвращаемый результат такие же, как и у метода match(): >>> regex = re.compile(r' [a-z] (6, }\s?\d', re.I) >>> regex.fullmatch('Python 3') <re.Match object; span=(O, 8), match='Python 3'>
Урок Регулярные выражения 11. >>> print (regex. fullmatch 185 ('Язык программирования Python 3')) None regex.fullmatch('Язык программирования >>> <re.Match object; span=(22, Python 3', 22, 30) 30), match='Python 3'> Класс мatch, представляющий результаты поиска, имеет функциональность после­ довательности и отображения. Представляемая им последовательность содержит: ♦ первый элемент - подстроку, совпавшую с самим регулярным выражением; ♦ последующие элементы - подстроки, совпавшие с нумерованными группами, которые входят в состав регулярного выражения. Если с какой-либо группой ничего не совпало, соответствующий элемент будет хранить None. Пример: >>> regex = re.compile(r' ([a-z]+)\s?(\d) ', >>> m = regex.search('Язык программирования re.I) Python 3') »> m[0], m[l], m[2] ( ' Python 3' , Огображение, 'Python' , '3' ) представляемое классом Match, содержит подстроки, совпавшие с именованными группами. Ключи этой последовательности совпадают с именами групп. Если с какой-либо группой ничего не совпало, соответствующий элемент будет хранить None: >>> regex >>> m = = re.compile(r' (?P<name>[a-z]+)\s?(?P<version>\d) ', regex.search('Язык программирования re.I) Python 3') >>> m[0], m['name'], m['version'] ('Python 3', 'Python', '3') Для примера напишем программу (листинг 11.1 ), проверяющую введенный адрес электронной почты на корректность с помощью регулярного выражения. Листинг 11.1. Программа, проверяющая корректность введенного адреса электронной почты import re regex print = re.compile(r'л([a-z0-9 ('Проверка корректности .-]+)@( ([a-z0-9-]+\.)+[a-z] {2,6})$', введенного адреса re.I} электронной почты') email = input ( 'Введите адрес электронной почты: ') m = regex.fullmatch(email} i f m: print ('Адрес', рrint('Ящик: m[ О], ', m[l], 'корректен') ',домен:' m[2], sep=") else: print ('Адрес некорректен') input ( 'Дпя выхода из программы нажмите <Enter>') Во втором выражении, создающем регулярное выражение, мы записали две вло­ женные друг в друга группы. Внешняя группа охватывает всю часть адреса, нахо-
Часть 186 дящуюся после символа @, а вложенная - 11. Язык Python: расширенные инструменты одну из подстрок, разделенных точками. У вложенной группы указан квантификатор + - поскольку таких подстрок в адресе может быть произвольное количество. 11.3.2. Поиск всех совпадений Для поиска в заданном тексте всех подстрок, совпавших с текущим регулярным выражением, нужно применять следующие два метода класса ♦ findall (<текст> [, <начало> [, <конец>]]) - • Pattern: возвращает: - если текущее регулярное выражение не содержит групп список со всеми совпавшими подстроками из заданного текста: >>> regex = re.compile(r'[a-z]{б,)\s?\d{l,4)', re.I) >>> regex.findall('Python З JavaScript 2022 Java 14') ['Python 3', 'JavaScript 2022'] • если текущее регулярное выражение содержит группы - список кортежей с подстроками, совпавшими с отдельными группами: >>> regex = re.compile(r'([a-z]{б,))\s?(\d{l,4))', re.I) >>> regex.findall('Python З JavaScript 2022 Java 14') [ ( 'Python', 'З'), ( 'JavaScript', '2022') ] • если совпадений не было - пустой список. Если заданы начало и конец, из указанного текста извлекается срез, и поиск про­ изводится в нем: ♦ f indi ter ( J - то же самое, что и f indall ( J, только возвращает итератор, вы­ дающий последовательность объектов класса мatch: >>> regex = re.compile(r'([a-z]{б,))\s?(\d{l,4))', re.I) >>> it = regex.finditer('Python 3 JavaScript 2022 Java 14') >>> for m in it: print(m[0], m[l], m[2], sep=', ') Python 3, Python, З JavaScript 2022, JavaScript, 2022 11.3.3. Прочие случаи использования Класс Pattern поддерживает еще ряд полезных методов: ♦ sub ( J - ищет в заданном тексте все подстроки, совпавшие с текущим регуляр­ ным выражением, заменяет их подстроками, сформированными согласно задан­ ному регулярному выражению, и возвращает результат. Формат вызова: suЬ(<регулярное выражение, Регулярное выражение, задающее замену>, задающее <текст>[, count=0]) замену указывается в виде обычной строки. В нем допускается использовать обратные ссылки форматов:
Урок 11. Регулярные выражения 187 \<порядковый номер труппы> \g< \g< <порядковый номер труппы> <имя труппы> > > Параметр count задает количество заменяемых подстрок. Если указать число О, заменены будут все подстроки. Примеры: >>> regex_l = re.compile(r'PHP') >>> regex_2 = r'Python' >>> regex_l.sub(regex_2, 'РНР РНР РНР РНР') 'Python Python Python Python' >>> regex_l.sub(regex_2, 'РНР РНР РНР РНР', count=2) 'Python Python РНР РНР' >>> regex_l = re.compile(r'<(\w+)>(.*?)<\/\1>') >>> regex_2 = r' [\g<l>]\2[/\g<l>]' >>> regex_l.sub(regex_2, '<strong>Python</strong>') '[strong]Python[/strong]' ♦ sр1it(<текст>[, дающему maxsp1it=O]) - с текущим регулярным разбивает заданный текст по символу, совпа­ выражением, и возвращает список получив­ шихся подстрок. Параметр maxsp1it задает количество подстрок, получившихся в результате раз­ биения. Если подстрок получится больше указанного количества, возвращаемый список будет содержать еще один элемент - с остатком строки. Если дать па­ раметру maxsp1it значение О, в возвращаемый список попадут все подстроки. Примеры: >>> regex = re.compi1e(r'[\s,\.]+') >>> regex.sp1it('Python, JavaScript. Java РНР, Ruby') [ 'Python', 'JavaScript', 'Java', 'РНР', 'RuЬy' ] >>> regex.sp1it('Python, JavaScript. Java РНР, Ruby', maxsp1it=2) ['Python', 'JavaScript', 'Java РНР, Ruby'] Для выполнения замены найденных подстрок в тексте также можно использовать метод expand () класса Match: ехраnd(<регулярное выражение, задающее замену>) Регулярное выражение следует указать в виде строки. В нем можно использовать об­ ратные ссылки форматов, приведенных в описании метода sub ( J . Метод возвращает результат замены. Пример: >>> >>> >>> >>> regex_l re.compile(r'<(\w+)>(.*?)<\/\1>') regex_2 r' [\g<l>] \2 [/\g<l>]' m = regex 1.search('<strong>Python</strong>') m.expand(regex 2) '[strong]Python[/strong]'
Часть 188 11.4. 11. Язык Python: расширенные инструменты Что еще нужно знать о регулярных выражениях и не только ♦ Оператор двоичного сложения (или двоичного ИЛИ) 1 (вертикальная черта) применяется для объединения целочисленных элементов перечислений. ♦ Функция purge () из модуля re удаляет из памяти промежуточные данные, со­ храняемые там в процессе обработки регулярных выражений. Ее рекомендуется вызывать после обработки большого количества регулярных выражений. 11.5. 1. Самостоятельные упражнения Перепишите программу из листинга 11.1, применив регулярное выражение с именованными группами. 2. Напишите новую версию программы-преобразователя из дюймов в сантиметры, которая для проверки корректности занесенного числа будет использовать регу­ лярное выражение.
Урок12 Установка и использование дополнительных библиотек язык очень мощный и развитый, вдобавок поставляется с богатой стан­ Python - дартной библиотекой. Однако такие операции, как обработка графики, работа с ин­ тернет-службами, взаимодействие с базами данных и многие другие ею не поддер­ живаются. Дт~я их выполнения придется устанавливать дополнительные библио­ теки. 12.1. Работа с дополнительными библиотеками. Утилита pip Дrlя работы с дополнительными библиотеками служит утилита в составе исполняющей среды Python. pip, поставляемая Она позволяет устанавливать библиотеки, удалять их при необходимости, а также выводить список установленных библио­ тек. Дrlя запуска утилиты pip <команда Команда pip следует набрать в консоли команду формата: и ее опции> указывает, [ <универсальные что должна сделать опции>] утилита. Поддерживаются следующие команды: ♦ установка библиотеки, соответствующей заданному указанию. Формат install команды: install [<опции команды>] <указание на библиотеку> Все устанавливаемые библиотеки автоматически загружаются из репозитория (хранилища) PyPf (Python Package Index, В качестве указания на реестр пакетов Python). библиотеку можно задать ее название - тогда будет установлена библиотека наиболее актуальной версии. Регистр символов, кото­ рым набрано название библиотеки, роли не играет. Пример установки самой «свежей» версии библиотеки pip install pillow 1 https://pypi.org/ Pillow:
Часть 190 11. Язык Python: расширенные инструменты Также можно написать указание, содержащее, помимо названия требуемой биб­ лиотеки, еще и сведения о ее версии. Записывается оно достаточно интуитивно. Вот несколько примеров: • pip install "pillow==ll.3.0" Будет установлена библиотека в репозитории Pillow строго версии 11.3.0. Если такая версия pip тут же завершит работу и выведет соот­ отсутствует, PyPI ветствующее сообщение; • pip install "pillow==ll.3" Будет установлена Pillow наиболее актуальной из версий 11.3. *. Опять же, если подходящая версия отсутствует, установка выполнена не будет; • pip install "pillow~=ll. 3. О" Будет установлена Pillow версии 11.3.0, а если такой нет сия• с наиболее актуальным номером модификации - имеющаяся вер­ ( 11.3 .1, 11.3 .2, 11.3 .8 и т. д.); • pip install "pillow~=ll.3" Будет установлена Pillow версии 11.3. *, а если такой нет - имеющаяся вер­ сия с наиболее актуальными номерами младшей версии и модификации (11.4.0, 11.4.1, 11.5.0 • pip install "pillow>=ll.3.0" Будет установлена 12.0.0 Команда • ит. д.); -u Pillow версии 11.3 .О или любая более новая (11.3.1, 11.4.0, и т. д.). install поддерживает ряд полезных опций: (или --upgracte) - обновление указанной библиотеки до наиболее акту­ альной версии: pip install -U pillow • --force-reinstall - полная переустановка заданной библиотеки. Использу­ ется совместно с опцией -u, если при обычном обновлении возникли проб­ лемы; • --cornpile - компиляция библиотеки после ее установки. Позволяет ускорить запуск программ, использующих эту библиотеку. ♦ l i s t - вывод списка установленных библиотек: > pip list Package Version pillow pip ♦ 11. 3. О 25.1.1 uninstall - удаление библиотеки с заданным названием. Формат команды: pip uninstall [<опции кома1-ЩЫ>] <название библиотеки>
Урок 12. Установка и использование дополнительных библиотек Утилита pip 191 выведет список папок, в которых хранятся модули, составляющие удаляемую библиотеку, и вопрос, действительно ли пользователь хочет ее уда­ лить. Чтобы подтвердить удаление, следует ввести латинскую букву «у», чтобы отменить его вишу - латинскую букву «n», после чего в любом <Enter>. Вот пример удаления библиотеки Pillow: случае нажать кла­ pip uninstall pillow Поддерживается полезная опция -у (или --yes), вызывающая немедленное уда­ ление библиотеки без вывода запроса. ♦ help - вывод справочных сведений о самой утилите pip или, если задана команда, об ЭТОЙ команде: pip help [<команда>] Примеры: pip help pip help install Из поддерживаемых универсальных опций наибольший интерес представляет опция -h (или --help), выводящая справочные сведения об указанной команде, всех ее опциях и универсальных опциях Вот пример вывода справки о команде pip. install: pip install -h 12.2. Библиотека Библиотека Pillow, Pillow: работа с графикой упоминавшаяся в разд. 12.1, служит для работы с графикой. С ее помощью мы можем открывать изображения, изменять их размеры, поворачи­ вать, обрезать, применять к ним различные фильтры и сохранять результаты на диске. Также можно создавать изображения с нуля. Установка библиотеки Pillow версии 11.3. *, описываемой в книге, выполняется вводом в консоли команды: pip install "pillow==ll. 3" Установив библиотеку, немного поэкспериментируем с ней в интерактивной обо­ лочке. Проверим, умеет ли она создавать миниатюры изображений. Найдем на диске или в Интернете какой-либо графический файл с изображением достаточно больших размеров, сохраним его в папке, например, c:\work и дадим ему имя, скажем, photo-source.jpg. Условимся дать создаваемой миниатюре размеры 32Ох240 пикселов и сохранить ее в той же папке в файле photo-thumbnail.jpg. В интерактивной оболочке импортируем модуль Image из пакета PIL, составляюще­ го упомянутую библиотеку': >>> frorn PIL irnport Irnage 1 Ранее библиотека Pillow носила название PIL. После изменения названия разработчики библиотеки оста­ - ради совместимости. вили имя ее главного пакета неизменным
Часть 192 11. Язык Python: расширенные инструменты Огкроем подготовленный ранее файл, применив функцию open () из модуля Image и передав ей в параметре путь к файлуl: >>> img = Image.open('c:/work/photo-source.jpg') Функция open () вернет объект, представляющий открытый файл. Создадим копию исходного изображения, вызвав у его объекта метод сору ( J : >>> img_copy = img.copy() Превратим полученную копию в миниатюру, вызвав у нее метод thШТ1Ьnail () с пе­ редачей ему кортежа с требуемыми размерами: >>> img_copy.thШТ1Ьnail((320, 240)) Сохраним получившуюся миниатюру, вызвав у нее метод >>> save (): img_copy.save('c:/work/photo-thШТ1Ьnail.jpg') После чего закроем оба файла - исходный и миниатюру, вызвав метод close () : >>> img_copy.close() >>> img. close () На заметку Полная документация по библиотеке Pillow находится по интернет-адресу: http://pillow.readthedocs.org/. 12.3. Программа для создания графических миниатюр, версия Изготовление миниатюр графических изображений 1.0 - операция достаточно рас­ пространенная. Напишем программу, которая будет ее выполнять. Для удобства разработки разобьем код программы на отдельные модули. Создадим где-либо папку, в которой будет храниться код программы, дав ей произ­ вольное имя. В этой папке создадим папку modules и поместим в ней пустой модуль _init_.py, тем самым превратив папку в полноценный пакет в разд. Python (о пакетах - 8.2). Начнем с создания модуля modules\thumbnailer.py, в который поместим объявление функции thШТ1Ьnailer (), создающей миниатюру. Вот формат вызова этой функции: thШТ1Ьnаilеr(<путь <путь к исходному файлу>, к папке для хранения миниатюр>, Пути задаются в виде строк, а размеры - <размеры миниатюры>) в виде последовательности из двух цело­ численных элементов: ширины и высоты, выраженных в пикселах. 1 В путях к файлам можно использовать как прямые, так и обратные слеши. Автор предпочитает прямые - с ними меньше проблем.
Урок 12. Установка и использование дополнительных библиотек Код модуля modules\thumbnailer.py приведен в листинге Листинг 12.1. Программа дпя modules\thumbnailer.py модуль 193 12.1. создания миниатюр, версия 1.0, from os.path import basename, join from PIL import Image def file_path, dest folder_path, sizes): filename = basename(src_file_path) dest file_path = join(dest_folder_path, filename) try: irпg = Image.open(src_file_path) img_copy = img.copy() thшnЬnailer(src # # # 1 2 3 # 4 img_copy.thшnЬnail(sizes) img_copy.save(dest_file_path) img_copy.close() img. close () except IOError: print ( f' Файл src _ file _path} 11 { 11 обработать не удалось') Сначала из пути к исходному файлу извлекаем имя этого файла с расширением, для чего применяем функцию basename() из модуля os.path (поз. 1 в листинге 12.1). Далее из пути к папке, где будут храниться миниатюры, и полученного имени фай­ ла составляем полный путь к файлу миниатюры, вызвав функцию join ( J из того же модуля (поз. 2). И создаем миниатюру знакомым по разд. 12.2 способом. Обязательно следует учесть, что в исходной папке могут оказаться поврежденные файлы, файлы в форматах, не поддерживаемых Pillow, или вообще не являющиеся графическими. При попытке открыть такой файл функция open () из модуля PIL. rmage возбудит исключение класса IOError. Мы поместили весь код, создаю­ щий миниатюру, в обработчик этого исключения (поз. 3). При возникновении ис­ ключения в консоли будет выведено соответствующее сообщение (поз. 4). Займемся стартовым модулем (модулем, который следует запустить для старта программы). По традиции, дадим ему имя start.py и поместим непосредственно в папку с разрабатываемой программой. Код стартового модуля приведен в листин­ ге 12.2. Листинг 12.2. Программа дпя создания миниатюр, версия стартовый модуль from os import scandir from os.path import isfile from modules.thшnЬnailer import print 1.0, start.py thшnЬnailer ('Создание миниатюр') src_fldr_path = inрut('Введите путь к папке с исходными файлами: ') dest fldr_path = inрut('Введите путь к папке для миниатюр: ') # 1
Часть 194 11. Язык Python: расширенные инструменты width = int(input('Bвeдитe ширину миниатюр: ')) height = int(input('Bвeдитe высоту миниатюр: ')) def do work(): for entry in scandir(src_fldr_path): if isfile(entry.path): thumЬnailer(entry.path, dest fldr_path, do work() input ( 'Для завершения программы нажмите # # # (width, height)) # # 2 4 5 6 3 <Enter>') Сначала запрашиваем у пользователя все сведения, необходимые программе для работы (поз. 1 в листинге 12.2). Код, непосредственно создающий миниатюры, мы оформляем в виде функции do work () (поз. 2), которую вызываем сразу же после объявления (поз. 3). В теле функции do _ work () просматриваем содержимое исходной папки, воспользо­ вавшись функцией scandir () из модуля os (поз. 4). Функция scandir () возвращает итератор, выдающий последовательность всех сущностей файлов и папок, - - которые хранятся по указанному в параметре пути. Каждая сущность представляет­ ся объектом, атрибут ра th которого содержит путь к этой сущности. Обязательно удостоверяемся, что очередная сущность является файлом. В этом нам поможет функция isfile () из модуля os. path (поз. 5). И наконец, на основе каждого файла делаем миниатюру, вызвав написанную ранее функцию thumЬnailer () из модуля modules\thumbnailer.py (поз. 6). Запустим только что написанную программу, зададим нужные ей сведения и по­ смотрим на созданные миниатюры. Мы только что написали очень полезную утилиту, которая может пригодиться нам в дальнейшем. Вот только пользоваться ею не очень удобно - все необходимые данные приходится вводить после ее запуска. Было бы замечательно, если бы все эти данные можно было бы задать непосредст­ венно при запуске программы 12.4. Модуль - в виде командных ключей. argparse: обработка командных ключей 12.4.1. Введение в командные ключи и их обработка Командные ключи указываются в составе команды, запускающей программу в кон­ соли, после имени программного файла и отделяются от него и друг от друга про­ белами. Посредством командных ключей в запускаемую программу передаются необходимые ей для работы данные. В состав стандартной библиотеки Python входит модуль argparse, предоставляю­ щий удобные средства для обработки командных ключей.
Урок 12. Установка и использование дополнительных библиотек 195 Модуль argparse поддерживает командные ключи, записанные в трех форматах: ♦ <значение> - обязательный командный ключ. Передает программе обязательное к указанию значение. Пример команды на запуск программы thumЬs с передачей ей двух обязательных командных ключей: thumЬs ♦ c:\work\source c:\work\thumЬnails --<полное имя> 1 -<сокращение> - опция (необязательный командный ключ) с за­ данным полным именем или сокращением. Можно указать либо полное имя, либо сокращение. В случае указания включает какой-либо режим работы запускаемой программы, в обычное время неактивный. Вот два примера передачи программе, помимо обязательного ключа, еще и опции, сначала имени, а потом - указанием ее полного - сокращения: pyc-deleter c:\work\projects --noecho pyc-deleter c:\work\projects -ne ♦ --<полное имя> <значение>~ -<сокращение> <значение> - опция со значением. По­ зволяет, наряду с включением связанного с ней режима работы программы, еще и передать заданное значение. Пример: pyc-deleter c:\work\projects --log c:\work\logs\pycd.log 12.4.2. Создание объекта обработчика командных ключей Объект обработчика командных ключей создается на основе класса ArgurnentParser из модуля argparse вызовом конструктора в формате: ArgurnentParser ( [prog=None] [, ] [usage=None] [, ] [description=None] [, [epilog=None] [, ] [argurnent_default=None] [, [add_help=True] [, ] [exit_on_error=True]) Параметры конструктора: ♦ prog - название программы в виде строки. Если указано значение None, исполь­ зуется имя стартового модуля без расширения; ♦ usage - описание формата команды, запускающей программу. Если указано None, генерируется на основе описания командных ключей (см. разд. ♦ description - ♦ epilog - 12.4.3); описание самой программы; примечания к описанию программы. В строках, передаваемых параметрам usage, description и epilog, можно ис­ пользовать специальный символ% (prog) s, обозначающий название программы; ♦ argurnent _ defaul t - значение по умолчанию для всех опций со значением, опи­ санных в текущем обработчике; ♦ add_help - если тrue, то при запуске программы с опцией --help или -h в кон­ соли будут выведены справочные сведения о программе, включающие ее назва-
Часть 196 11. Язык Python: расширенные инструменты ние (задается в параметре prog), формат запускающей ее команды (usage), опи­ сание самой программы ных ключей (см.разд. (description), 12.4.3) описание поддерживаемых ею команд­ и примечания (epilog). Если False, справочные сведения выводиться не будут; ♦ если True, то в случае задания неподдерживаемого командного exi t _ on_ error - ключа или некорректного значения программа завершится с выдачей кода за­ вершения 2. Если False, то в этом случае программа завершаться не будет, а при обработке командных ключей возникнет исключение класса ArgumentError из модуля argparse, которое можно обработать. Пример: from argparse import ArgumentParser ArgumentParser(prog='PYC deleter', usage='pyc-deleter ар= <путь> description='Yдaлeниe [<опции>]' РУС-файлов', epilog='Moя первая программа 12.4.3. на Python! ') Описание командных ключей Создав обработчик командных ключей, следует описать командные ключи, кото­ рые он должен поддерживать. Для добавления описания очередного командного ключа служит метод add_ argument () класса ArgumentParser: 1>, <имя 2>, . . . , <имя N>[, ] [action='store'] [, [nargs=None] [, ] [const=None] [, ] [default=None] [, ] [type=str] [, ] [choices=None] [, ] [required=False] [, [help=None] [, ] [metavar=None] [, ] [dest=None]) add_argument(<имя Этот метод принимает много параметров: ♦ позиционные - имя создаваемого обязательного ключа или имя и сокращение создаваемой опции: ар. add _argument ( 'path') ap.add_argument('--no-echo', ♦ action - '-ne') действие, которое будет выполнено со значением, переданным через создаваемый командный ключ: • 'store' - значение будет сохранено в одноименном ключу атрибуте объек­ та, полученного в результате обработки командных ключей (см.разд. • 'store_ const' - 12.4.4); в атрибуте объекта с результатами будет сохранено значе­ ние, заданное в параметре const. Применяется при описании опций: ap.add_argument('--no-echo', • 'store_true' и 'store_false' хранены значения True и False ap.add_argument('--no-echo', '-ne', action='store_const', const=True) в атрибуте объекта с результатами будут со­ соответственно: '-ne', action='store true')
Урок • 12. Установка и использование дополнительных библиотек 197 в атрибуте объекта с результатами будет сохранен список, и зна­ 'append' - чение создаваемого ключа добавлено в него. Применяется при создании опций, которые возможно указать несколько раз с разными значениями, - и все эти значения будут добавлены в упомянутый список: ap.add_argument('--file-ext', action='append') # pyc-deleter c:\work --file-ext рус --file_ext tmp • 'append_const' - то же самое, что и 'append', только в список добавляется значение параметра • const; аналогично 'append', только в список добавляются все значения, 'extend' - указанные после опции через пробел. Параметру nargs следует дать значение '*' или '+': ap.add_argument('--file-exts', action='extend', nargs='+') # pyc-deleter c:\work --file-exts рус tmp bak • 'help' - выводятся справочные сведения о программе. Применяется, если при создании объекта обработчика командных ключей параметру add_help было дано значение False: ap.add_argument('--show-me-help', action='help') ♦ правила обработки значений создаваемого ключа: nargs • None - единственное значение, указанное у ключа, заносится в соответст­ вующий атрибут результирующего объекта; • если значение указано, оно заносится в атрибут результирующего объ­ '?' - екта, если не указано - заносится значение параметра default. Применяется при создании опций, которые могут указываться как со значениями, так и без них: ap.add_argument('--log', nargs='?', default='c:/pycd.log') # pyc-deleter c:\work --log c:\logs\deleter.log # pyc-deleter c:\work --log • применяется при указании у параметра action значения 'extend'. Тре­ '+' - бует, чтобы было задано хотя бы одно значение: ap.add_argument('--file_exts', action='extend', nargs='+') • ♦ аналогично '*' - defaul t - ' +', только допускается не указывать ни одного значения; значение по умолчанию. Если None, будет использовано значение па­ раметра argument default конструктора класса ArgumentParser (см.разд. 12.4.2): ap.add_argument('--log', default='c:/pycd.log') # pyc-deleter c:\work --log c:\logs\deleter.log # pyc-deleter c:\work ♦ type - допустимый тип значения. Если заданное значение не может быть пре­ образовано в этот тип, при обработке командных ключей возникнет исключение класса ArgumentTypeError из модуля argparse: ap.add_argument('--log-max-size', type=int, default=l048576)
198 ♦ Часть choices - 11. Язык Python: расширенные последовательность допустимых значений для создаваемого ключа: ap.add_argument('--log-rnax-size-unit', choices=('B', default='B') ♦ инструменты 'КВ', 'МВ'), принимается во внимание лишь при создании опции. Если тrue, required - будет создана опция, обязательная к указанию (в этом случае значение парамет­ ра defaul t будет проигнорировано). Если False, создаваемую опцию можно не указывать (тогда она получит значение, заданное в параметре default): ap.add_argument('--log-path', required=True) Обязательный командный ключ, как следует из его названия, является обяза­ тельным к указанию, и значение параметра ♦ help - required в его случае игнорируется; справочные сведения о создаваемом ключе в виде строки. В их тексте могут быть использованы специальные символы %(prog) s (обозначает название программы), %(default) s, %(type) s и %(choices) s (значения параметров default, type и choices соответственно): ap.add_argument('path', required=True, hеlр='Путь ap.add_argument('--log', default='c:/pycd.log', hеlр='Путь ♦ dest - к файлу журнала (по умолчанию: к очищаемой папке') %(default)s) ') имя атрибута объекта, в котором будут сохранены результаты обработки командного ключа, в виде строки. Если None, атрибут получит имя по умолча­ нию (их формирование будет описано в разд. 12. 4. 4): ap.add_argument('--log-rnax-size', dest='rnaxlogsize') ♦ rnetavar - строка, которая отображается в описании параметра в том месте, где нужно поставить его значение. Если None, будет использовано значение пара­ метра 12.4.4. dest. Обработка командных ключей Обработка командных ключей, указанных при запуске программы, выполняется вызовом метода parse _ args () класса ArgumentParser. Метод возвращает объект класса Narnespace, содержащий результаты обработки. Возвращенный объект содержит атрибуты, хранящие значения командных ключей. Имя каждого такого атрибута: ♦ ♦ берется из параметра dest метода add_argument () (см. разд. если этот параметр не был указан - 12.4.3); используется самое длинное из имен, задан­ ных в позиционных параметрах метода add_ argument () . Символы в начале имени, удаляются, а находящиеся в середине имени в символы подчеркивания. Пример: frorn argparse irnport ArgumentParser ArgumenrParser( . . . ) ар= - -, стоящие преобразуются
Урок ар. 12. Установка и использование дополнительных библиотек 199 add_ argшnent ( 'ра th' ) '-ne', action='store_true') dest='maxlogsize') ap.add_argшnent('--no-echo', ap.add_argшnent('--log-max-size', params = ap.parse_args() # Возвращенный объект будет # maxlogsize содержать атрибуты: path, по echo и Если был указан неподдерживаемый командный ключ или обязательному ключу не было дано значение, то: ♦ если при создании обработчика командных ключей параметру было дано значение тrue чей кода завершения ♦ exi t _ on_error программа немедленно прекращает работу с выда­ 2; если параметру exi t _ on_ error было дано значение False ключение класса ArgшnentError из модуля возбуждается ис­ argparse: from argparse import ArgшnentError try: params = ap.parse_args() except ArgшnentError: print ('Укажите правильные командные ключи') Если у командного ключа было указано значение типа, отличного от заданного в параметре type метода add_argшnent () (см.разд. 12.4.3), то при обработке ключей будет возбуждено исключение ArgшnentTypeError из модуля argparse: from argparse import ArgшnentTypeError try: params = ap.parse_args() except ArgшnentTypeError: рrint('Неподходящий тип 12.5. значения') Программа для создания графических миниатюр, версия Напишем версию 2.0 2.0 программы для создания миниатюр (см.разд. 12.3), которая будет получать необходимые для работы сведения через следующие командные ключи и опции: ♦ первый обязательный ключ - путь к папке с исходными файлами; ♦ - путь к папке, в которой будут сохранены миниа­ второй обязательный ключ тюры; ♦ --width <ширина в виде целого числа в пикселах> по умолчанию 320; необязательный, значение
Часть 200 ♦ /1. Язык Python: --height <высота в виде целого числа в пикселах> по умолчанию ♦ расширенные инструменты необязательный, значение 240; --no-warnings или -nw - отключает вывод предупреждений о невозможности обработать очередной файл. Сначала исправим модуль modules\thumbnailer.py, добавив в функцию thшnЬnailer () новый параметр no _ warnings. Значение True, переданное в этом параметре, подавит вывод предупреждения о необрабатываемом файле (листинг Листинг 12.3. Программа для создания миниатюр, версия модуль modules\thumbnailer.py (исправления) def thumЬnailer(src_file_yath, 12.3). 2.0, dest_folder_yath, sizes, no_warnings): except IOError: if not no_warnings: print(f'Фaйn "{src_file_yath}" обработать не удалось') В стартовый модуль start.py добавим код, создающий обработчик командных клю­ чей и выполняющий их обработку, и удалим код, который запрашивал данные у пользователя (листинг Листинг 12.4. 12.4). Программа для создания миниатюр, версия стартовый модуль 2.0, start.py (исправления) fran argparse import ArgumentParser print ( 'Создание ар ар. миниатюр') = Argumentparser () add_ argument ( 'source' , help=' Путь к пaruce с исходиыни изображениями' , metavar= 'исходная-папка' ) ap.add_argument( 'destination', hеlр='Путь к паmсе дпя миниа'l'JСIР', metavar=' папка-назначения') ар. add_argument ( '--width' , type=int, defaul t=320, help=' Ширина (ПИJCce.m.i) ' , metavar= 'ПDq>ИНа • ) ap.add_argument( '--height', type=int, default=240, help= ' высота (ПИJCce.m.i) ' , metavar= ' высота ' ) ар. add_ argument ( '--no-warnings' , '-nw' , action=' store_ true' , hеlр='Оrк.mачить предупреждения о необработанных файлах') р = ap.parse_args() src_fldr_yath, dest_fldryath, width, height, no_warnings = \ р. source, р. destination, р. width, р. height, р. no_ warnings sre fldr_path height iAput ( 'Введ.1'!'е пу"Fь iflt ( iF!pUt ( 1 BBC.!Dl'FC 1€ паm(е ВЫСО'FУ l•l>Шlta'F!8p: с 11е1ю;1щь11,s1 1) ) фай.яа~,1>1: ' )
Урок 12. Установка и использование дополнительных библиотек 201 def do work(): for entry in scandir(src_fldr path): if isfile(entry.path): thumЬnailer(entry.path, dest_fldr___J>ath, (width, height), no_ warnings) Запустим новую версию программы из консоли без командных ключей и увидим следующий вывод: > start.py Создание миниатюр usage: start.py [-h] [--width ширина] [--height высота] ~ [--no-warnings] исходная-папка папка-назначения start.py: error: the following argurnents are required: ~ исходная-папка, папка-назначения Запустим программу с командным ключом --help или -h и посмотрим на выведен­ ные ею справочные сведения: > start.py --help Создание миниатюр высота] usage: start.py [-h] [--width ширина] [--height [--no-warnings] исходная-папка папка-назначения ~ Создание миниатюр positional argurnents: исходная-папка Путь к папке папка-назначения Путь к options: -h, --help --width ширина --height высота --no-warnings, -nw папке с изображениями исходными для миниатюр show this help rnessage and exit Ширина (пикселы) Высота (пикселы) Отключить предупреждения о необработанных файлах Наконец, попробуем указать все требуемые командные ключи, например: start.py "с:\Мои фотографии" c:\work\thшnЬnails --width 640 --height 480 --no-warnings ~ и проверим, как работает новая версия программы. 12.6. Что еще нужно знать о дополнительных библиотеках ♦ Дополнительные библиотеки устанавливаются по пути C:\Users\<Имя учетной зanucu>\AppData\Roaming\Python\Python<Hoмep вepcuu>\site-packages.
Часть 202 ♦ 11. Язык Python: расширенные инструменты В состав некоторых библиотек входят различные консольные программы. Эти программы записываются в папку С:\Usегs\<Имя учетной зanucu>\AppData\Roaming\ Python\Python<Hoмep вepcuu>\Scripts. ♦ Если исполняющая среда Python установлена в папку, отличную от C:\Program Files (х86) или C:\Program Files, то устанавливаемые библиотеки записываются Python>\Lib\site-packages, а программы, входящие в их со­ став, в папку <путь установки Python>\Scripts. в папку <путь установки ♦ Модули код Python также можно писать на языке С++ и компилировать в машинный для повышения производительности. Такие модули сохраняются в фай­ - лах с расширением pyd. Значительная часть кода библиотеки Pillow реализована именно в виде руd­ модулей. 12. 7. 1. Самостоятельные упражнения Просмотрите код версии 12.4) и 2. 2.0 программы для создания миниатюр (листинги 12.3- найдите фрагменты, являющиеся явно лишними. Оптимизируйте код. Напишите версию 2.1 этой программы, добавив поддержку опции без значения --verbose, которая включает вывод в консоли сообщения о каждом обработан­ ном файле. 3. Измерьте время выполнения программы для создания миниатюр на каком-либо наборе изображений. Используйте для этого декоратор profiler () тинг 6.1 ), (см. лис­ сохранив его в отдельном модуле. У автора на основе набора из 51 изображения миниатюры с размерами по умол­ чанию (32Ох240 пикселов) бьmи созданы за 5,132 с.
Урок13 Многопоточное и многопроцессное программирование Программа для создания миниатюр, написанная нами на уроке 12, не отличается высокой производительностью. Безусловно, несколько десятков картинок она об­ работает за считанные секунды. Но когда количество обрабатываемых изображе­ ний станет исчисляться сотнями и тысячами, программа «призадумается» надолго ... Причиной невысокого быстродействия программы является то, что изображения обрабатываются последовательно, одно за другим. И обработка следующего изо­ бражения начинается лишь по окончании обработки предыдущего. Однако мы можем сделать так, чтобы изображения обрабатывались одновременно, сразу по нескольку штук. Тогда операционная система распределит их обработку по разным процессорным ядрам, что существенно повысит производительность. Параллельное выполнение 11 одновременное вьшолнение нескольких фрагментов программного кода или нескольких экземпляров одного и того же фрагмента кода. 13.1. Многопоточное программирование Введение в потоки. 13.1.1. Глобальная блокировка интерпретатора Поток - своего рода виртуальный процессор, на котором выполняется один из фрагментов кода программы. Количество одновременно выполняемых потоков не ограничено. Все существующие в программе потоки выполняются одним экземпляром исполняю­ щей среды и используют один массив оперативной памяти. Главный поток - поток, создаваемый при запуске программы самой операционной системой и исполняющий ее основной код. Руthоn-программы, которые мы писали ранее, выполнялись в единственном, глав­ ном потоке. На этом уроке мы будем писать многопоточные программы. Преимущества потоков: ♦ простота обмена данными между потоками - поскольку все потоки выполня­ ются одной исполняющей средой и используют один массив памяти; ♦ быстрый запуск;
204 ♦ Часть малый расход системных ресурсов - 11. Язык Python: расширенные инструменты каждый поток отнимает относительно не- много оперативной памяти. Недостаток у потоков один, но очень серьезный. В текущий конкретный момент исполняющая среда может выполнять лишь один 11 поток кода, написанного на Python, - остальные в это время приостанавливаются. Это сделано намеренно, чтобы избежать одновременного обращения к одним и тем же переменным из разных потоков, что может привести к повреждению данных, хранящихся в этих переменных, и краху программы. Такова особенность реализа­ ции Python, носящая Global Interpreter Lock). название глобальной блокировки интерпретатора (GIL, Операции ввода/вывода, выполняемые в потоке, не приводят к приостановке других 11 потоков. То есть в то время, когда какой-либо поток производит чтение из файла или от­ правку данных по сети, могут выполняться другие потоки. Дело в том, что опера­ ции ввода/вывода реализуются не исполняющей средой Python, а операционной системой, на которую не распространяется глобальная блокировка интерпретатора. Так что, если в потоках, помимо всего прочего, выполняются операции файлового или сетевого ввода/вывода, мы все равно получим существенную прибавку произ­ водительности. Если же в потоках производятся обычные вычисления, выигрыша от многопоточности не будет никакого. 13.1.2. Создание потоков Поток представляется классом Thread из модуля threading стандартной библиотеки. Конструктор класса имеет следующий формат вызова: Thread(target=None[, name=None] [, args=()] [, kwargs=(}] [, daemon=None]) Параметры конструктора: ♦ target - ссылка на функцию или метод другого объекта, который и реализует код, выполняющийся в потоке. Эта функция (метод) может принимать произ­ вольные параметры; ♦ name - имя потока в виде строки. Если None, исполняющая среда сама даст соз­ даваемому потоку имя формата: Тhrеаd-<порядковый номер потока> ~ (<имя функции (метода) из параметра target>) Имя у потока обычно задают при отладке программы - чтобы отличить один поток от других; ♦ args - кортеж, элементы которого при запуске потока будут переданы функции (методу) из параметра target в позиционных параметрах; ♦ kwargs - словарь, элементы которого при запуске потока будут переданы функ­ ции (методу) из параметра target в именованных параметрах;
Урок ♦ 13. Многопоточное и многопроцессное программирование 205 если None или False будет создан обычный поток, если True - daemon - поток­ служба. Программа перед завершением будет ждать, когда завершат выполнение все обычные потоки, но не потоки-службы. Последние будут продолжать работу даже после того, как программа завершится. Теперь рассмотрим методы класса Thread: ♦ запускает выполнение текущего потока. После вызова этого метода start () - начинает выполняться функция (метод), заданная в параметре target конструк­ тора. Запущенный поток завершится, когда упомянутая функция (метод) закончит выполняться. Запустить завершившийся поток повторно нельзя нию исключения типа ♦ - это приведет к возбужде­ RuntimeError; j oin ( [ timeout=None] ) - приостанавливает выполнение потока, в котором был вызван этот метод: если параметру timeout было дано значение None - • до тех пор, пока текущий (у которого был вызван метод) поток не завершится. если в параметре timeout был указан промежуток времени в виде веществен­ • ного числа в секундах - пока не истечет этот промежуток времени. По истечении заданного промежутка времени проверить, работает ли еще текущий поток, можно вызовом метода is _ ali ve (); ♦ is _ ali ve () - возвращает True, если текущий поток еще работает, и False - в противном случае. Могут оказаться полезными следующие атрибут и свойство класса Thread: ♦ ♦ name - атрибут, имя текущего потока; свойство, True, если текущий поток является службой, и False, если daemon - это обычный поток. Можно изменить разновидность потока (например, превратить обычный поток в службу) уже после его создания, присвоив этому свойству новое значение. Только делать это следует перед вызовом метода start (). Немного поэкспериментируем с потоками: >>> >>> >>> >>> >>> >>> from threading import Thread # Создаем поток, которой вьmолняющий передаются встроенную функцию позиционные параметры print (), 1, 2, 3 sep со значением ' 1 ' t = Thread(target=print, args=(l, 2, 3), kwargs={'sep': t.start() и именованный параметр l 1 2 1 3 >>> t.is alive() False ' 1 '})
Часть 206 11. Язык Python: расширенные инструменты >>> t.name 'Thread-4 (print)' 13.1.3. Блокировки Предположим, что мы написали новую версию программы для создания миниатюр. В ее коде мы сначала просматриваем содержимое заданной исходной папки и зано­ сим каждый содержащийся в ней файл в заранее созданный список. Далее объявля­ ем функцию, которая выбирает из этого списка файлы и создает на их основе миниатюры. И наконец, запускаем несколько потоков, каждый из которых выпол­ няет экземпляр этой функции. В коде нашей программы к одному и тому же списку обращаются разные потоки. И рано или поздно наступит момент, когда несколько потоков попытаются обра­ титься к списку одновременно. Но обычные списки Python (объекты класса list), равно как и все типы этого языка, не приспособлены к такому поведению. Несколь­ ко одновременных попыток обращения к одному и тому значению могут повредить его, что закончится возникновением ошибки и крахом программы. Чтобы избежать такой коллизии, следует предписать всем потокам обращаться к единому списку файлов (или любому другому критическому ресурсу) строго по одному. Как? Использовать блокировку. Блокировка - механизм, гарантирующий, что в каждый момент времени доступ к ка­ кому-либо критическому ресурсу имеет лишь один поток из всех запущенных в про­ грамме. Каждому потоку, обращающемуся к критическому ресурсу, следует передать ссыл­ ку на созданный заранее объект блокировки. Непосредственно перед обращением к ресурсу поток активирует полученную блокировку - и может безопасно рабо­ тать с ресурсом. Все остальные потоки приостановятся, пока блокировка не будет деактивирована. Закончив работу с ресурсом, первый поток деактивирует блоки­ ровку, чтобы дать доступ к ресурсу остальным потокам. Блокировка представляется объектом класса Lock из модуля threading. Конструктор этого класса вызывается без параметров. Класс Lock предоставляет три метода: ♦ acquire( [Ыocking=True] [, ] [timeout=-1]) - активирует текущую блокировку, если она не была ранее активирована другим потоком, и возвращает тrue. Если же блокировка уже активирована: • если параметру Ыocking дано значение тrue - приостанавливает поток, в ко­ если параметру timeout дано значение -1 - пока блокировка, наложенная тором бьm вызван этот метод: 0 другим потоком, не будет деактивирована. Тогда метод активирует ее и возвращает 0 True; если в параметре timeout указано значение временного промежутка в виде положительного вещественного числа в секундах - пока не истечет этот
Урок 1З. 207 Многопоточное и многопроцессное программирование промежуток времени. Если блокировка будет деактивирована до истече­ ния промежутка времени, метод активирует ее и возвращает тrue, в про­ тивном случае ничего не делает и сразу возвращает • если параметру Ыocking дано значение вращает ♦ False - False; ничего не делает и сразу воз­ False; деактивирует ранее активированную блокировку. Если блокировка release () - не была активирована, возбуждает исключение класса RuntimeEror. Пример: from threading import Lock locker = Lock () locker. acquire () # Работаем с критическим locker. release () ♦ locked () - ресурсом возвращает True, если текущая блокировка активирована, и False - в противном случае. Класс Lock является менеджером контекста, так что приведенный пример можно записать так: with locker: # Работаем 13.1.4. с критическим ресурсом Программа для создания графических миниатюр, версия 3.0 Напишем очередную версию программы для создания графических миниатюр, основанную на версии 2.0 (см.разд. 12.5). В ней процесс создания миниатюр «рас­ параллелим» на четыре потока. Начнем с написания функции runner 1), которая будет выполняться в потоках. С параметрами она получит список файлов из исходной папки, объект блокировки и объект со значениями командных ключей, переданных программе при запуске. Функция будет в цикле извлекать из полученного списка файл за файлом и делать на его основе миниатюру. Ее код мы сохраним во вновь созданном модуле modules\runner.py (листинг 13 .1 ). IJII,. 13.1. Прогр~~:с§здания миниатюр. аерсия 3.0, модуль modulel\runner.py from .thurrЬnailer import thurrЬnailer def runner(filelist, locker, params):
208 Часть 11. Язык Python: расширенные инструменты while True: # locker. acquire () # try: el = filelist.pop(O) # except IndexError: break # finally: locker. release () # thшnЬnailer(el, pararns.destination, # (pararns.width, pararns.height), pararns.no_warnings) 1 2 3 6 4 5 Для обработки полученного списка файлов применим бесконечный цикл с услови­ ем (поз. 1в листинге 13.1, подробности о списке с условием - в разд. 3.5). В его теле перед обращением к списку файлов активируем блокировку, чтобы нам не по­ мешал какой-нибудь другой поток (поз. 2). Потом в обработчике исключений пыта­ емся извлечь из списка первый элемент (поз. 3). В случае успеха обязательно деак­ тивируем блокировку, тем самым давая доступ к списку другим потокам (поз. и обрабатываем извлеченный из списка файл и вследствие этого возникло исключение типа мым завершая выполнение функции runner () (поз. 4), 5). Если же список пуст, IndexError, прерываем цикл, тем са­ и соответственно потока (поз. опять же, не забыв деактивировать блокировку (поз. 6), 4). Исправим стартовый модуль. В нем мы создадим список с файлами, подлежащими обработке, список с четырьмя потоками, запустим эти потоки и приостановим вы­ полнение стартового модуля до тех пор, пока все потоки не завершатся (лис­ тинг 13.2). Листинг 13.2. Программа дпя создания миниатюр, версия 3.0, стартовый модуль start.py (исправления) from modules.thumlэAailer import thumlэAailer from threading i.mport Тhread, Lock from modules.runner i.mport runner р = ap.parse_args() sre_fldr_path, dest fldr_path, ·,,idth, height, Ao_·,юrAiAgs \ p.source, p.destiAatioA, p.·..·idth, p.height, p.Ao_1юrAiAgs def do_work(): filelist = [entry.path for entry in scandir(p.souroe) \ if isfile(entry.path)] locker = Lock О threads = [Тhread(target=runner, args=(filelist, locker, for r in range (4)] for t in threads: t.start() # # р)) 1 2 \ # # 3 4
Урок 13. Многопоточное и многопроцессное программирование 209 for t in t.hreads: t.join() # 5 do_work () Для создания списка файлов, подлежащих обработке, применим списковое вклю­ чение (см.разд. 5.1.4) - это повысит производительность (поз. Подготовим объект блокировки (поз. 2). 1в листинге 13.2). Создадим четыре потока, каждому из которых передадим список файлов, объект блокировки и объект с командными ключами. Созданные потоки поместим в другой список, для чего также применим списковое включение с диапазоном (поз. данные потоки (поз. 4) 3). Далее останется лишь запустить соз­ и подождать их завершения (поз. 5). У автора эта версия программы на том же наборе файлов работала существенно быстрее предыдущей. 13.1.5. Очереди Применять список для хранения перечня заданий для потоков неудобно - для уст­ ранения коллизий приходится задействовать блокировку. Гораздо удобнее исполь­ зовать очереди. 11 Очередь - аналог списка, пригодный для использования в нескольких потоках 1 . Наиболее часто применяются очереди, реализуемыми двумя классами из модуля queue: ♦ SimpleQueue упрощенная очередь неограниченного размера, позволяющая - лишь хранить перечень каких-либо позиций. Конструктор класса вызывается без параметров; ♦ Queue - полноценная очередь, имеющая дополнительную функциональность. Размер такой очереди можно ограничить, чтобы сэкономить память. Конструк­ тор класса вызывается в формате: Queue ( [maxsize=O]) Параметр maxsize задает размер создаваемой очереди. Если задать значение О, будет создана очередь неограниченного размера. Для работы с очередями применяются следующие методы классов SimpleQueue и Queue: ♦ put () - добавляет заданное значение в конец текущей очереди. Результата не возвращает. Формат вызова: put (<значение> [, Ьlock=True] [, timeout=None]) Параметры Ыосk и tirneout принимаются во внимание только классом Queue, только если текущая очередь имеет ограниченный размер и на текущий момент заполнена. В этом случае метод: 1 Программисты называют такие типы данных потокобезопасными.
210 • Часть 11. Язык если параметру Ыосk дано значение тrue - Python: расширенные инструменты приостанавливает выполнение потока, в котором он вызван: 0 если в параметре timeout передано значение None - до тех пор, пока в очереди не освободится место; □ если в параметре timeout ного числа в секундах передан промежуток времени в виде веществен­ - до тех пор, пока не истечет этот промежуток времени. Если в очереди так и не появилось свободное место, метод воз­ буждает исключение типа Full из модуля queue; • если параметру Ыосk дано значение False ние ♦ put_nowait (<значение>) метру Ыосk значения ♦ сразу же возбуждает исключе­ Full; get ( [Ыock=True] [, аналогичен вызову метода put () с передачей пара­ False; ] [timeout=None]) - извлекает из текущей очереди первый элемент и возвращает его. Если текущая очередь пуста, метод: • если параметру Ыосk дано значение True - приостанавливает выполнение потока, в котором он вызван: 0 если в параметре timeout передано значение None - до тех пор, пока в очереди не появится хотя бы один элемент; 0 если в параметре ного числа в timeout секундах - передан промежуток времени в виде веществен­ до тех пор, пока не истечет этот промежуток времени. Если в очереди так и не появилось ни одного элемента, метод возбуждает исключение типа Empty из модуля queue; • если параметру Ыосk дано значение False ние ♦ get _ nowai t (<значение>) метру Ыосk значения ♦ сразу же возбуждает исключе­ Empty; empty () - - аналогичен вызову метода get () с передачей пара­ False; возвращает True, если текущая очередь пуста, и False - в противном случае; ♦ qsize () - возвращает приблизительный размер текущей очереди. Доверять результатам, выданным этими методами, не стоит. В любой момент любой другой поток может добавить элемент в очередь или извлечь элемент из очереди, в результате чего ее размер изменится. Следующие методы поддерживаются лишь классом Queue: ♦ full () - возвращает тrue, если текущая очередь заполнена, и False, если в ней еще есть свободное место. Доверять возвращенному результату также не стоит по описанной ранее причине; ♦ task_ done () - сообщает текущей очереди, что извлеченный из нее ранее эле­ мент успешно обработан. Должен вызываться в том же потоке, который извлек элемент;
Урок ♦ 211 Многопоточное и многопроцессное программирование 13. приостанавливает выполнение потока, в котором он был вызван, пока j oin () - все элементы, хранящиеся в текущей очереди, не будут извлечены и успешно обработаны. 13.1.6. Программа для создания графических миниатюр, версия 3.1 13.1.4), 30 создания миниатюр, основанная на версии 3.0 для хранения перечня файлов, подлежащих обработке, будет ис­ пользовать очередь класса равным для программы версия Новая (см. разд. Queue. Чтобы сэкономить память, мы зададим ее размер элементам'. А заполнение очереди файлами и их выборку из очереди будем проводить параллельно. Сначала перепишем функцию runner () , выполняющуюся в потоках и, собственно, производящую обработку файлов. Теперь функция будет принимать два параметра: ссылку на очередь с файлами и объект с командными ключами. Признаком того, что подлежащих обработке файлов в исходной папке больше нет, послужит нали­ чие в очереди элемента, хранящего пустую строку (листинг Листинг модуль from 13.3. Программа для modules\runner.py .thumЬnailer import создания миниатюр, версия 13.3). 3.1, thumЬnailer def runner(filequeue, params ) : while True: el = filequeue.get() # ) # filequeue.task_done( if el == '': # break thumЬnailer ( el, params.destination, # (params.width, params.height), params.no _warnings) 1 2 3 4 В бесконечном цикле выбираем очередной элемент из очереди, полученной с пара­ метром (поз. 1 в листинге обработан (поз. 2). 13.3), и сразу же оповещаем очередь о том, что элемент Если полученный элемент - пустая строка, т. е. признак того, что обрабатывать больше нечего, прерываем цикл и тем самым завершаем поток (поз. (поз. 3). 4). Если же из очереди извлечен путь к файлу, обрабатываем этот файл Исправим стартовый модуль, применив один весьма примечательный программи­ стский прием, описываемый далее (листинг 1 Автор 13.4). тестировал эту программу на наборе из 51 файла. Если вы , уважаемый читатель, используете для этой цели набор из большего количества файлов, можете увеличить размер очереди.
212 Часть Листинг 13.4. 11. Язык Python: расширенные Программа дпя создания миниатюр, версия стартовый модуль инструменты 3.1, start.py (исправления) from threading import Тhread from queue import Queue def do work(): filequeue = Queue(maxsize=ЗO) threads = [Тhread(target=runner, args=(filequeue, р)) \ for r in range(4)] for t in threads: t.start() [filequeue.put(enuy.path) for enuy in scandir(p.source) \ if isfile (enuy .path)] [filequeue.put(' ') for r in range(4)] for t in threads: t.join() # 1 # 2 # 3 # # # 5 4 6 do_work() Первым делом формируем очередь размером 30 элементов (поз. 1в листинге 13.4). Далее создаем и сводим в список четыре потока, которым передаем в параметрах только что созданную очередь и объект с командными ключами (поз. каем эти потоки (поз. 3). 2), и запус­ Запущенные потоки будут приостановлены в ожидании, когда в очереди появятся пути к обрабатываемым файлам. Теперь об упомянутом ранее программистском приеме. Для наполнения очереди путями к файлам мы применили списковое включение (поз. 4). Оно перебирает итератор, возвращенный функцией scandir (), и каждый элемент генерируемой им последовательности добавляет в очередь вызовом метода put (). В качестве резуль­ тата это списковое включение вернет список из значений None (этот список нам совершенно не нужен, и мы его нигде не сохраняем). Как видим, списковые вклю­ чения часто применяются не только для создания новых последовательностей на основе существующих, но и для обработки элементов заданных последовательно­ стей без создания новых. Далее с применением того же приема добавляем в очередь четыре пустые строки по одной на каждый из потоков (поз. 5). - Эти пустые строки послужат сигналами для потоков прекратить выполнение. Остается лишь приостановить остальные четыре потока (поз. На взгляд автора, версия 3 .1 основной время, пока не завершатся программы для создания миниатюр имеет примерно такое же быстродействие, как и версия очереди экономится память. поток на 6). 3.0. Однако за счет уменьшенного размера
Урок 13. Многопоточное и многопроцессное программирование 13.2. 213 Многопроцессное программирование 13.2.1. Процесс Процессы и работа с ними - своего рода виртуальный компьютер, на котором выполняется один из фрагментов кода программы. Количество создаваемых процессов не ограничено. Каждый процесс содержит свой собственный экземпляр исполняющей среды и свою собственную память, изолированную от памяти остальных процессов. Главный процесс - процесс, создаваемый операционной системой при запуске про­ граммы. У процессов есть существенное преимущество перед потоками: все запущенные процессы в каждый момент времени выполняются одновременно. Так как каждый процесс выполняется собственным экземпляром исполняющей среды, на него не действует глобальная блокировка интерпретатора (см. разд. 13.1.1). Недостатки процессов: ♦ сложности с обменом данными ственным экземпляром - поскольку каждый процесс выполняется соб­ исполняющей среды, изолированным от других ее экземпляров. Для пересьmки данных от одного процесса к другому придется применять оче­ реди; ♦ довольно медленный запуск; ♦ существенный расход системных ресурсов - каждый процесс занимает доволь- но много места в памяти. Процесс представляется классом Process из модуля multiprocessing. Конструктор этого класса вызывается так же, как и конструктор класса Thread (см.разд. 13.1.2). Класс Process поддерживает методы start(), join(), is_alive(), атрибут name и свойство daemon, характерные для класса Thread. Также могут пригодиться: ♦ атрибут, целочисленный код завершения текущего процесса или exi tcode - None, если процесс еще работает; ♦ close () - метод, принудительно завершает текущий процесс с освобождением всех занимаемым им системных ресурсов. В коде процессов можно использовать блокировки, представляемые классом Lock из модуля multiprocessing. Этот класс поддерживает методы acquire () и release (), аналогичные методам его «тезки» из модуля queue (см.разд. 13.1.3). В том же модуле multiprocessing существуют два класса очередей, предназначен­ ных для использования совместно с процессами: ♦ SimpleQueue (см.разд. ♦ - полный аналог одноименного класса из модуля queue 13.1.5); JoinaЬleQueue - полный аналог класса Queue из модуля queue. Код, создающий новые процессы, нужно выполнять, лишь удостоверившись, что текущий модуль был запущен, а не импортирован (как это сделать, описано
214 Часть в разд. 8.3). Язык Python: расширенные инструменты Кроме того, предварительно следует вызвать функцию freeze _ support () mul tiprocessing - из модуля 11. она подготовит исполняющую среду к созданию но­ вых процессов. Это обусловлено особенностями Python. Пример: from multiprocessing import freeze_support, Process if name == ' main freeze _ support () Process ( р . ) Исполняющая среда создает новый процесс путем копирования текущего процесса (обычно главного) со всем содержимым его памяти. Вследствие этого функции, выполняющиеся в процессах, могут использовать любые глобальные переменные, функции и классы, объявленные в коде программы. Программа для создания графических миниатюр, 13.2.2. версия 4.0 Напишем еще одну версию программы для создания миниатюр, носящую но­ мер 4.0. На этот раз параллельное выполнение кода реализуем на основе процессов. Нам понадобится переписать лишь стартовый модуль (листинг 13 .5). Весь осталь­ ной код останется неизменным. Листинг 13.5. Программа дпя создания миниатюр, версия стартовый модуль 4.0, start.py (исправления) from threadin§ import Thread from queue import Queue from multiprocessing i.пrport freeze_support, Process, JoinaЬleQueue def prepare(): # l # 2 # # 3 # # 5 6 # 7 ар= ArgumentParser(description='Coздaниe миниатюр') return ар. parse_ args О defdo_work(p): filequeue = JoinaЬleQueue(maxsize=ЗO) processes = [Process(target=runner, args=(filequeue, for r in range (4)] for t in processes: р)) 4 \ t.start() for t in processes: t. join () do_·,юrk()
Урок if 215 Многопоточное и многопроцессное программирование 13. name = ' main freeze_ support () print ( 'Создание миниа.тq>' ) р = prepare () do_work(p) input ( 'Для завершения программы # 8 # 9 # 10 # 11 нажмите <Enter>') Для удобства оформим код, обрабатывающий командные ключи, в виде функции prepare () (поз. 1 в листинге 13 .5). Объект с командными ключами будет возвра­ щаться этой функцией в качестве результата (поз. 2). Функция do _work () теперь будет принимать с единственным параметром объект с командными ключами (поз. 3). В ее теле создаем очередь, представляемую объек­ 4), и четыре процесса (поз. 5), запускаем эти процес­ ожидаем их завершения (поз. 7). том класса JoinaЫeQueue (поз. сы (поз. 6), после чего Проверяем, был ли стартовый модуль запущен (поз. 8), и, если это так, вызываем функцию freeze _ support (), чтобы подготовить исполняющую среду к созданию новых процессов (поз. 9). Далее обрабатываем командные ключи, получаем объект, содержащий их значения (поз. 10), и запускаем обработку файлов (поз. 11). На компьютере автора эта версия программы выполнилась примерно за то же вре­ мя, что и предыдущая, несмотря на то, что запуск процессов потребовал допол­ нительного времени. Это достигнуто благодаря тому, что процессы, в отличие от потоков, работают одновременно в любой момент времени. Что еще нужно знать 13.3. о потоках и процессах ♦ При запуске любой программы, не обязательно написанной на операционной системой создается главный процесс, а в нем - Python, самой главный поток, в котором и исполняется программа. А уже в главном процессе программа может создать другие потоки и процессы. ♦ В любом стороннем процессе, запущенном из главного процесса, может быть создано произвольное количество потоков. ♦ Для оптимизации производительности следует создавать потоки и процессы в количестве, чуть меньшем количества процессоров или процессорных ядер, имеющихся в компьютере. Узнать количество процессоров (процессорных ядер) можно, вызвав функцию cpu_ count () из модуля os стандартной библиотеки: >>> frorn os irnport cpu_count >>> cpu_count() 8
Часть 216 13.4. 1. 11. Язык Python: расширенные инструменты Самостоятельные упражнения Измерьте время выполнения версии 3.0 программы для создания миниатюр на наборе изображений, примененном ранее (см.разд. все тот же декоратор 12. 7). Используйте для этого profiler (). У автора на основе того же набора изображений миниатюры были созданы за 2. 1,7626 с. Измерьте точно таким же образом время выполнения версии (у автора- 3. 4. этой программы 1,8199 с). Измерьте точно таким же образом время выполнения версии (у автора- 3 .1 4.0 этой программы 1,8514 с). Попробуйте в версии 3.1 программы увеличить размер очереди до измерьте время ее выполнения (у автора - 1, 7668 с). 40 и снова
Урок14 Конкурентное программирование Глобальная блокировка интерпретатора позволяет исполняющей среде в один момент времени выполнять лишь один поток с Руthоn-кодом (подробности в разд. 13.1.1). - Однако на операции ввода/вывода это ограничение не распространя­ ется, поскольку их выполняет не исполняющая среда Python, а операционная сис­ тема. Следовательно, пока в одном потоке, например, считывается файл, будет вы­ полняться другой поток. А ведь разные фрагменты кода вполне могут выполняться в одном-единственном потоке по очереди. Представим себе функцию, в которой производится чтение - файла. Как только дело доходит до выражения, читающего файл, выполнение функции приостанавливается, и начинает выполняться другой код или даже другой экземпляр той же самой функции. Когда считывание файла завершается, выполне­ ние функции возобновляется. Конкурентное выполнение 11 выполнение разных фрагментов кода или разных экземпляров одного и того же кода в одном потоке по очереди. При конкурентном выполнении нам не нужно тратить процессорное время на соз­ дание дополнительных потоков или процессов и расходовать оперативную память на их хранение. Налицо экономия системных ресурсов при существенном повыше­ нии производительности. Поскольку код, выполняющийся конкурентно, может быть приостановлен на неоп­ ределенный срок, невозможно предугадать момент, когда он выдаст результат. Го­ ворят, что конкурентный код выполняется асинхронно, и конкурентное выполнение также часто называют асинхронным. 14.1. Сопрограммы 14.1.1. Введение в сопрограммы. Фьючеры СопрогрШt4ма 1 11 - функция, предназначенная для конкурентного выполнения. Формат объявления: async def <тело <имя функции>( функции> 1 По-английски coroutine. [ <имя параметра 1>, . . . , <имя парам. N>] ):
Часть 218 11. Язык Python: расширенные инструменты Фактически объявление сопрограммы отличается от объявления обычной функции (см. разд. 6.2.1) лишь наличием ключевого слова async. Сопрограммы, запущенные друг за другом в одном фрагменте кода, выполняются 11 строго последовательно, в порядке их запуска, т. е. не конкурентно. Ранее говорилось, что невозможно предугадать момент времени, когда сопрограм­ ма выдаст результат. Поэтому сопрограмма сразу после запуска возвращает в каче­ стве результата фьючер. Фьючер 11 объект, ~возвращаемый сопрограммой и представляющий результат ее выполнения, которыи будет получен некоторое время спустя. Чтобы приостановить выполнение кода до того момента, когда сопрограмма, вер­ нувшая фьючер, выработает результат, применяется оператор приостановки await: await <фьючер, возвращенный сопроrраммой> Встретив этот оператор, исполняющая среда приостанавливает выполнение кода до момента, когда сопрограмма, вернувшая заданный фьючер, не выработает результат. Во время приостановки будут выполняться другие сопрограммы. Когда первая со­ программа выработает результат, оператор приостановки вернет его и возобновит выполнение кода. Оператор приостановки awai t может использоваться лишь в сопрограммах. Попытка применить его в обычном коде (в том числе и в обычной функции) приведет к возбу­ ждению исключения типа syntaxError. Для эксперимента объявим сопрограмму wai t _ for (<задержка>), которая приоста­ навливает выполнение кода на заданную в секундах задержку и возвращает строку, содержащую эту задержку. Для приостановки slеер(<задержка>), объявленную в модуле кода применим сопрограмму asyncio стандартной библиотеки. Не забудем указать у результата, возвращенного этой сопрограммой, оператор приос­ тановки awai t, иначе выполнение нашей сопрограммы wai t _ for () не будет приос­ тановлено: >>> frorn asyncio irnport sleep >>> async def wait_for(delay): await sleep(delay) return f'Задержка: {delay} с.' Для вызова сопрограммы из обычного кода предназначена функция run () из того же модуля asyncio: run(<фьючер, Функция возвращенный сопроrраммой>) run () вызывает сопрограмму, возвратившую указанный фьючер, дожидает­ ся завершения ее выполнения и возвращает выданный ею результат. Проверим, как работает объявленная нами сопрограмма wai t _ for (), запустив два ее экземпляра с разными значениями задержек. Чтобы они запустились одновремен­ но, оформим запускающий их код в виде еще одной сопрограммы с именем
Урок 14. Конкурентное программирование 219 run _ coros () . Чтобы измерить время выполнения кода, вставим в начало и конец из модуля time, возвращающей тела сопрограммы вызовы функции strftime () текущее время: >>> >>> >>> >>> >>> from asyncio import run from time import strftime # Объявляем сопрограмму run_coros (), запускающую экземпляры # сопрограммы wai t_ for () async def run_coros(): # Выводим время начала вьmолнения сопрограммы run_coros () print ( strftime ( '%Х') ) # Запускаем первый экземпляр сопрограммы wait_for(), дожидаемся # окончания ее работы и возврата ею результата и # выводим возвращенньrn результат в консоли res = await wait for(l) print (res) # Аналогично вьmолняем второй экземпляр сопрограммы wai t_ for () print(await wait for(З)) # Выводим время окончания вьmолнения сопрограммы run_coros () print ( strftime ( '%Х' ) ) >>>#Запускаем сопрограмму run coros() >>> run(run_coros()) 11:23:58 Задержка: 1 с. Задержка: 3 с. 11:24:02 Видно, что код выполнялся 4 секунды, что есть сумма продолжительности выпол­ нения обоих экземпляров сопрограммы wait for () (1 секунда и 3 секунды). Мы только что убедились, что все сопрограммы, запущенные в одном участке кода, выполняются последовательно. Для выполнения сопрограмм в обычном коде также можно использовать класс Runner из модуля asyncio. Этот класс является менеджером контекста. Его конст­ руктор вызывается без параметров. Для запуска сопрограммы используется метод run () класса Runner, имеющий такой же формат вызова, что и описанная ранее одноименная функция. Пример: >>> from asyncio import Runner >>> wi th Runner () as runner: runner.ruп(run coros()) . Результат тот же, что и ранее ... Сопрограммы не требуют особых усилий для написания и запуска, однако выпол­ няются строго последовательно, друг за другом. Во многих случаях это становится серьезным недостатком.
Часть 220 1/. Язык Python: расширенные инструменты Сопрограммы-методы 14.1.2. Сопрограмму также можно создать на основе метода класса. Сопрограмма-метод объявляется так же, как и сопрограмма-функция (см. разд. 14.1.1). Вот пример: >>> class Cls: # Объявляем сопрограмму-метод async def wait_for(self, delay): await sleep(delay) return f'Задержка {delay} с.' >>> async def run_coros(): obj = Cls () print(await obj.wait for(l)) print(await obj.wait for(З)) >>> run(run_coros()) Задержка 1 с. Задержка 3 с. 14.1.2.1. Асинхронные итераторы. Цикл перебора с приостановкой Можно разд. написать 10.2.4), асинхронный класс-итератор (об итераторах рассказано в выдающий элементы заданной последовательности асинхронно. Для этого в классе нужно объявить следующие dunder-мeтoды: ♦ ♦ ai ter _ anext _ () - обычный метод, аналог метода () - сопрограмма-метод, аналог метода i ter _ (); next _ () . По окончании по- следовательности должна возбудить исключение типа StopAsynciteration. Для перебора асинхронного итератора следует использовать цикл перебора с при­ остановкой, который записывается в формате: async for <тело <переменная> in <строка>: цикла> (else: <блок else>] Напишем асинхронный итератор, который выдает целые числа от I до 5 с задерж­ кой, равной выданному числу и выраженной в секундах. После чего переберем этот итератор и выведем выдаваемые им числа в консоли: >>>#Объявляем асинхронный итератор >>> class Aiter: (self): aiter def i = О self. return self (self): anext async def self. i += 1
Урок 14. Конкурентное программирование if self. 221 i > 5: raise StopAsynciteration else: await sleep(self. i) return self. i >>>#пишем сопрограмму, перебирающую этот итератор >>>#в цикле перебора с приостановкой >>> async def run_aiter(): async for i in Alter(): print(i, end=' ') >>> run(run aiter()) 1 2 3 4 5 14.1.2.2. Асинхронные менеджеры контекста. Асинхронный обработчик контекста Имеется также (см. разд. 10.3.2), возможность написать асинхронный менеджер контекста который выдает и обрабатывает контекст асинхронно. В таком классе необходимо объявить следующие сопрограммы-методы: ♦ aenter ♦ aexi t () () - аналог метода аналог метода enter exi t (); () . Для работы с контекстом, выданным асинхронным классом, применяется обработ­ чик контекста с приостановкой. ется наличием ключевого слова Or async обычного, описанного в разд. перед словом 9.2, он отлича­ wi th. Для примера напишем менеджер контекста, который выдает список, позволяет до­ бавить в него целые числа, а потом выводит эти числа в консоли, каждое держкой, равной этому числу и выраженной в секундах: >>>#Объявляем асинхронный менеджер контекста >>> class AМgr: async def aenter self. lst = [] (self): return self async def aexit (self, exc_type, exc_value, traceback): for el in self.lst: await sleep(el) print(el, end=' >>>#Объявляем сопрограмму, ') работающую с этим менеджером контекста >>> async def run_amgr(): async with AМgr() as amgr: amgr.lst.append(l) amgr.lst += (2, 3] - с за­
Часть 222 11. Язык Python: расширенные инструменты >>> run(run_amgr()) 1 2 З 14.1.3. Python Сопрограммы-генераторы позволяет объявлять сопрограммы-генераторы, аналогичные по функцио­ нальности генераторам-функциям (см.разд. 10.1). Для примера напишем сопрограмму-генератор, выдающую заданное количество последовательно увеличивающихся целых чисел, начиная с 1, каждое - с задерж­ кой, равной этому числу и выраженной в секундах: >>> frorn asyncio irnport sleep, run >>>#Объявляем сопрограмму-генератор >>> def delays(count): for r in range(l, count + 1): await sleep(r) yield r аsупс >>>#Объявляем сопрограмму, >>> запускающую этот генератор def run_delays(): async for i in delays(4): print(i, end=' ') аsупс >>> run(run_delays()) 1 2 З 4 14.2. Задача Задачи - дальнейшее развитие сопрограммы. Задачи, запущенные друг за другом в одном фрагменте кода, выполняются полностью конкурентно (одновременно). 14.2.1. Создание и выполнение задач Для создания задачи применяется функция create task () из модуля asyncio: возвращенный сопрограммой>) create_task(<фьючep, Функция создает на основе сопрограммы, вернувшей указанный фьючер, задачу, представленную объектом класса Task, запускает ее на выполнение и возвращает в качестве результата. Созданная таким образом задача имеет функциональность фьючера. Следователь­ но, к ней может быть применен оператор приостановки awai t (см.разд. 14.1), и из нее может быть извлечен результат, выданный сопрограммой. Создадим две задачи на основе двух экземпляров сопрограммы wai t _ for (), напи­ санной в разд. 14.1.1. Создающий их код оформим в виде сопрограммы run_tasks (), которую запустим уже знакомым нам способом:
Урок 14. Конкурентное программирование 223 >>> from asyncio import sleep, run, create task >>> from time import strftime >>> async def run_tasks(): print (strftime ( '%Х')) # Сначала создаем обе задачи ... taskl = create_task(wait_for(l)) task2 = create- task(wait - for(З)) # ... а потом запускаем их, дожидаемся их # и выводим выданные ими результаты resl = await taskl print (resl) res2 = await task2 print(res2) print (strftime ( '%Х' ) ) завершения >>> run(run_tasks()) 17:36:32 Задержка: 1 с. Задержка: 3 с. 17:36:35 Прекрасно видно, что время выполнения кода составило должительности выполнения самого «долгоиграющего» 3 секунды, что равно про­ экземпляра сопрограммы wai t _ for () (3 секунды). Налицо заметный выигрыш в производительности по срав­ нению с сопрограммами. 14.2.2. Одновременный запуск задач Если требуется запустить созданные задачи на выполнение строго одновременно, следует использовать сопрограмму gather (<фьючер 1>, <фьючер 2>, gather () ... , из модуля <фьючер asyncio: N> [, return_exceptions=False]) В позиционных параметрах упомянутой сопрограмме передаются фьючеры, возвра­ щенные задачами или сопрограммами (которые будут автоматически преобразова­ ны в задачи), подлежащими запуску. Параметр return _ exceptions мы рассмотрим позже. Сопрограмма возвращает фьючер со списком результатов, выданных выполнивши­ мися задачами. Перепишем пример из разд. экземпляр сопрограммы не будем превращать - 4.2.1 с применением сопрограммы gather (). Первый wai t _ for () предварительно превратим в задачу, а второй он будет преобразован в задачу автоматически: >>> from asyncio import gather >>> async def run_tasks(): print (strftime ( '%Х'))
Часть 224 11. Язык Python: расширенные print(await gather(create task(wait for(l)), wait print (strftime ( '%Х' ) ) >>> run(run_tasks()) 13:53:43 [ 'Задержка: 1 с.', 13:53:46 'Задержка: 3 инструменты for(З))) с.'] Напишем сопрограмму, которая возвращает квадрат заданного числа после задерж­ ки, выбранной случайно в диапазоне от этой сопрограммы, задав им числа 1 до 5 секунд. Запустим четыре экземпляра 5, 1О, 15 и 20, и выведем результаты в консоли: >>> from random import randint >>>#Объявляем сопрограмму squared(), вычисляющую квадраты чисел >>> async def squared(val): # Дnя вычисления псевдослучайной задержки используем # функцию randint() из модуля random (см. разд. 4.1.3.3) await sleep(randint(l, 5)) return val ** 2 >>>#Объявляем сопрограмму, вьmолняющую четыре экземпляра squared() с разными параметрами >>> async def run_squared(): # Создаем итератор, вьщающий последовательность из четырех # экземпляров сопрограммы squared(), у которых заданы разные # параметры, применив генератор-выражение (см. разд. 5.4.2) s = (squared(el) for el in (5, 10, 15, 20)) # Распаковываем последовательность сопрограмм из # созданного итератора в вызове функции gather() и # дожидаемся результатов выполнения этих сопрограмм print(await gather(*s)) >>>#сопрограммы >>> run(run_squared()) [25, 100, 225, 400] 14.2.3. Группы задач Другой способ одновременно запустить заданные задачи задач, представленной классом - использование группы TaskGroup из модуля asyncio. Этот класс является асинхронным менеджером контекста (см. разд. 14.1. 2. 2), а его конструктор вызыва­ ется без параметров. Для создания отдельной задачи в теле обработчика контекста применяется метод create_ task () класса тaskGroup. Формат его вызова такой же, как и у одноименной функции (см.разд. 14.2.1). Как только завершится выполнение блока, входящего в состав обработчика контек­ ста, все задачи, созданные в этом блоке, запустятся на выполнение, а выполнение кода приостановится до того момента, когда все они выполнятся.
Урок 14. 225 Конкурентное программирование Метод create task (), как и одноименная функция, возвращает объект класса Task, представляющий созданную задачу. Дr~я получения результата, возвращенного этой задачей, можно использовать метод resul t () класса тask. Пример: >>> from asyncio import TaskGroup >>> async def run_tg(): # Используем асинхронный обработчик контекста (подробности # в разд. 14.1.2.2), в котором добавляем задачи в созданную # группу async with TaskGroup() as tg: taskl = tg.create_task(squared(5)) task2 = tg.create task(squared(l0)) # Для извлечения результатов, выданных задачами, используем # метод resul t () print(taskl.result(), task2.result()) >>> run(run_tg()) 25 100 14.2.4. Прерывание задач Дr~я прерывания выполнения задачи применяется метод cancel ( [msg=None]) класса Task. Этот метод возбуждает исключение типа cancelledError из модуля asyncio. Его следует обработать в теле сопрограммы, на основе которой создана задача, после чего возбудить повторно оператором raise (см.разд. 9.1.3), чтобы его можно было обработать в коде, создавшем задачу. В параметре msg метода cancel () можно указать строку с сообщением, которое будет занесено в возбуждаемое исключение. В качестве примера создадим, запустим, тут же прервем задачу и обработаем воз­ никшее исключение: >>>#Объявляем сопрограмму, реализующую задачу >>> async def some coro(): # Обрабатываем исключение типа CancelledError try: await sleep(l00) except CancelledError as ехс: print(exc) # Повторно возбуждаем исключение, чтобы его # обработать в запускающей сопрограмме raise >>>#Объявляем сопрограмму, создающую, >>> async def run_some_coro(): task = create task(some coro()) можно было запускающую и прерывающую задачу
226 Часть 11. Язык Python: расширенные # После создания задачи await sleep(l) # ... и прервем задачу подождем секунду ... task.cancel(msg='Зaдaчa прервана') # Код, приостанавливающий задачу, try: await task except CancelledError as ехс: print(exc) инструменты поместим в обработчик исключения >>> run(run some coro()) Задача прервана Задача прервана Сообщение было выведено дважды, поскольку исключение тывалось и в сопрограмме run _ some _ coro (), 14.3. some _ coro (), CancelledError обраба­ реализующей задачу, и в сопрограмме которая ее запустила. Асинхронные очереди В модуле asyncio объявлен класс Queue, реализующий очередь, специально предна­ значенную для использования в конкурентном коде. Формат вызова конструктора этого класса такой же, как и у конструктора одноименного класса из модуля (см.разд. 13.1.5). Этот класс queue get_nowait(), join (), характер­ поддерживает методы put_nowait(), empty (), qsize (), full (), task_ done (), сопрограммы put (), get () и ные для упомянутого класса очереди. 14.4. Библиотека aiofiles: конкурентная работа с файлами Модуль asyncio стандартной библиотеки предоставляет мощные и высокоэффек­ тивные конкурентные программные инструменты для сетевого ввода/вывода. Однако для работы с файлами он, к сожалению, не содержит никаких средств. Однако существует дополнительная библиотека aiofiles, предназначенная для 24.1.*, описываемую в курентной работы с файлами. Установить ее версию кон­ этой книге, можно подачей в консоли команды: pip install "aiofiles==24.l" На заметку Полная документация по библиотеке https://github.com/Тinche/aiofiles. aiofiles доступна по интернет-адресу: К сожалению, конкурентная работа с файлами уступает по производительности многопоточной, многопроцессной и даже обычной, выполняемой в одном потоке и процессе. Все дело в том, что библиотека aiofiles выполняет операции файлового
Урок 14. Конкурентное программирование 227 ввода/вывода в отдельных потоках, создание которых требует дополнительного процессорного времени и оперативной памяти 1 • 14.5. Программа для создания графических миниатюр, версия 5.0 Попрактикуемся в конкурентном программировании, написав еще одну версию программы для создания графических миниатюр 2 . В этом случае придется переде­ лать все три модуля, из которых состоит программа Начнем с модуля modules\thumbnailer.py (листинг ... 14.1). Листинг 14.1. Проrрамма дпя создания миниатюр, версия модуль moduln\thumbnaller.py (исправления) 5.0, from aiofiles i.mport open from io import ВytesIO async def thumЬnailer(src_file_path, dest_folder_path, sizes, # 1 # # 2 3 4 5 6 no_waпiings) : filenarne = basenarne (s r c_file_path ) dest_file_pa th = j oin (dest_folder_path, filenarne ) tr y : i.mg_data = None async with open(src_file_path, mode='rb') as src file : i.mg_data = await src_file.read() src_ i.mg_buffer = ВytesIO {i.mg_data) i.mg = Image.open(src_i.mg_Ьuffer) img_copy = # # # img . copy () img_co p y. thшnЬnail ( sizes ) dest_i.mg_buffer = ВytesIO() i.mg_сору. save (dest_i.mg_buffer, format=' JPEG') async with open(dest_file_path, mode='wЬ') as dest_file: awai t dest_ file. wri te (dest_ i.mg_Ьuffer. getЬuffer () ) # 7 # 8 # 9 # 1О img_copy. cl ose () img . c l ose () except I OError: Поскольку функция thшnЬnailer () будет выполняться конкурентно, обязательно преобразуем ее в сопрограмму (поз. 1 в листинге 14.1 ). Создаем переменную, в которой будет храниться содержимое считываемого исход­ ного файла, присвоив ей значение None (поз. любое значение 1 Программисты - 2). В принципе, ей можно присвоить оно в дальнейшем никакой роли не играет. называют подобные неэффективные решения «костылями». этой программы будет чрезвычайно низкой . Но это только для примера . 2 Производительность
Часть 228 Для конкурентного открытия файла 11. Язык Python: расширенные применяется функция aiofiles, входящего в состав одноименной библиотеки (поз. open () 3). инструменты из модуля В качестве пара­ метров она принимает путь к открываемому файлу и обозначение режима его от­ крытия в виде строки. Режим 'rb' предписывает открыть файл для чтения в двоич­ ном режиме. Функция open () является менеджером контекста, выдающим в качест­ ве контекста объект, представляющий открытый файл. В теле обработчика контекста конкурентно считываем файл, вызвав у упомянутого объекта сопрограмму-метод read() (поз. 4). Сопрограмма-метод вернет содержимое открытого файла в виде последовательности байтов типа bytes. Это содержимое присваиваем переменной, созданной ранее. К сожалению, библиотека Pillow не может открывать файлы, представленные по­ следовательностью байтов. Ее придется предварительно преобразовать в поток - своего рода файл, только хранящийся в оперативной памяти. Такой поток пред­ ставляется объектом класса BytesIO из модуля io стандартной библиотеки. Нам достаточно передать последовательность байтов конструктору этого класса (поз. 5). Чтобы открыть файл, представленный полученным потоком, достаточно передать его знакомой нам функции open () из модуля PIL. Image ( поз. 6). Чтобы конкурентно сохранить файл со сгенерированной миниатюрой, сначала сле­ дует создать пустой поток в виде объекта класса Bytesro, вызвав конструктор этого класса без параметров (поз. 7). Далее сохраняем миниатюру в этот поток, применив знакомый нам метод save () объекта изображения (поз. 8). Оrметим, что упомяну­ тому методу необходимо передать с параметром format строковое обозначение формата сохраняемого файла, поскольку библиотека Pillow сама выяснить его не сможет. Чтобы упростить программу, будем сохранять миниатюры только в фор­ мате JPEG. Далее точно так же, как и ранее, функцией open () из модуля aiofiles, конкурентно открываем еще не существующий файл, в котором должна храниться миниатю­ ра, файл будет автоматически создан (поз. - задаем строку 'wb' - 9). В качестве формата открытия открытие для сохранения в двоичном режиме. В теле обработчика контекста производим конкурентное сохранение миниатюры в открытом файле, вызвав у него сопрограмму-метод wr i te () ( поз. также подстерегает «засада» - 1О). Здесь нас этот метод не работает с потоками. Поэтому нам предварительно нужно извлечь из потока содержимое файла, представленное по­ следовательностью байтов типа bytes, вызвав метод getbuffer () класса Bytesro. В коде тинг модуля modules\runner.py переделки будут менее значительными (лис­ 14.2). Листинг модуль 14.2. Программа для создания rnodules\runner.py (исправления) async def runner(filequeue, params): while True: миниатюр, версия 5.0, # l
Урок 229 14. Конкурентное программирование el = await filequeue.get() await params.destination, (params . width, params .height), params . no_ warnings) thumЬnailer(el , # 2 # з Функция r unne r () также будет выполняться конкурентно, следовательно , подлежит преобразованию в сопрограмму (поз . 1 в листинге 14.2). В стартовом модуле (см . далее листинг 14.3) для хранения перечня преобразуемых файлов мы применим конкурентную очередь Queue из модуля asyncio. Метод get () этой очереди является сопрограммой , поэтому мы должны приостановить выпол­ нение кода, пока он не завершит работу (поз . 2). Также мы должны приостановить выполнение кода на время , пока выполняется сопрограмма thumЬna i l er () (поз. 3). Листинг 14.3. Программа для создания миниатюр, версия стартовый модуль start.py (исправления) fr om multiproces s in~ i mpo rt fr ee ze_ s upport, Proce s s, import asyncio 5.0, J oin aЬl eQueu e async def do_ work () : filequeue = asyncio . Queue() [filequeue.put_nowait(entry.path) \ for entry in scandir(p.source) if isfile(entry . path)] [filequeue.put_nowait(' ') for r in range(4)] tasks = (asyncio.create_task(runner(filequeue, р)) \ for r in range(4)) await asyncio . gather(*tasks) asyncio.run(do_work()) Функцию do_ wo rk () преобразуем в сопрограмму (поз. # # # 1 2 # # 4 5 # # 7 з 6 1 в листинге 14.3) . . Создаем конкурентную очередь на основе класса Queue из модуля async i o (поз . 2). Ради простоты сделаем ее неограниченного размера. Наполняем очередь файлами (поз. 3). Также для простоты выполняем это с помо­ щью обычного метода put_nowait () очереди. Точно таким же образом помещаем в очередь четыре пробела тить работу (поз. (см . разд. сигналы экземплярам сопрограммы runne r () прекра­ последовательность Далее создаем сопрограммы - 4). runne r () 5.4.2) . (поз . 5), из четырех задач, содержащих экземпляры использовав для этого генератор-выражение Потом запускаем созданные задачи, распаковав полученную по­ следовательность задач в вызов сопрограммы gat her () (поз . Наконец, запускаем сопрограмму do work () (поз . 7). 6).
Часть 230 14.6. 1. 11. Язык Python: расширенные инструменты Самостоятельные упражнения Измерьте время выполнения версии 5.0 программы для создания миниатюр на том же наборе изображений, что использовался для тестирования предыдущих версий программы. Декоратор profiler () ( см. листинг 6.1) для этой цели, к сожалению, не подой­ дет. Он выдаст время выполнения лишь той части кода, которая не является конкурентной. А эта часть кода исполняется настолько быстро, что с точки зре­ ния декоратора выполнится за О секунд. Вставьте код, изменяющий производительность, непосредственно в тело сопро­ граммы do _ work (), вот так: from ti.me import ti.me async def do_work(): Ьegin = ti.me О filequeue = asyncio.Queue() await asyncio.gather(*tasks) end = ti.me О print(f'Вpeмя выполнения: {end - У автора миниатюры были созданы за 2. Увеличьте в версии 5.0 Ьegin: .4f) с.') 5,064 с. программы количество выполняющихся задач до вторно измерьте время выполнения программы (у автора - 5,195 6 и по­ с). Вероятно, при увеличении количества задач они начинают мешать друг другу выполняться.
Урок15 Аннотации типов и документирование кода. Дата классы При создании переменной или атрибута объекта можно указать, значения какого типа эта переменная или атрибут будет хранить. В объявлении функции можно поставить указания на типы значений, передаваемых ей с параметрами, и тип воз­ вращаемого ею результата. А еще непосредственно в объявлении функции можно записать руководство по ее использованию. И наконец, для хранения каких-либо сущностей, каждая из которых содержит стро­ го определенный набор значений строго определенных типов, можно использовать датаклассы. 15.1. 11 Аннотации типов и их применение Аннотация типа - 15.1.1. указание на требуемый тип значения. Написание аннотаций типов Аннотация типа у переменной, атрибута, обычного или классового, или параметра функции или метода записывается в следующем формате: <переменная>l<атрибут>l<параметр> : <тип данных> Примеры: # Переменная, хранящая целочисленные значения i: int = 1 # Функция, принимающая параметр строкового типа def func(a: str): class Cls: # Классовый атрибут вещественного типа attr: float = 123.456 # Метод, принимающий параметр типа def method(self, values: list): list (список)
232 Часть 11. Язык Python: расширенные инструменты Аннотация типа у результата, возвращаемого функцией или методом, записывается в формате: def -> <имя> ([<параметры>]) <тип данных>: Отметим, что тип данных ставится перед завершающим двоеточием. Примеры: # Функция, возвращающая результат def func(a: str) -> bool: в виде логической from datetime import date class Cls: # Метод, возвращающий объект класса date def method(self, values: list) -> date: величины из модуля datetime Если аннотация не задана, допускается значение любого типа. Наиболее часто аннотации указываются у параметров функций, методов и типов возвращаемых ими результатов. 15.1.1.1. Указание типов в аннотациях Типы в аннотациях можно указать в виде: ♦ простой ссылки на тип: i: int = 1 def func(a: str) -> bool: ♦ <тип 2> <тип 1> 1 . . # Переменная, способная j: int I float = 45.7 . 1 <тип N> - хранить # Функция, возвращающая либо def func2() -> str I bool: ♦ None - целые строку, любой из приведенных типов: или вещественные числа либо логическую величину задается в качестве типа возвращаемого результата у функции (метода), не возвращающей результата явно: def ♦ funcЗ(b: int, с: tuple) -> None: одного из классов, объявленных в модуле typing стандартной библиотеки: • AnyStr - строка или последовательность байтов типа bytes: import typing def load_file(path: str, mode: str) -> typing.AnyStr:
Урок • 233 Аннотации типов и документирование кода. Датаклассы 15. строка, либо указанная непосредственно, либо полученная Literalstring - непосредственно указанной конкатенацией строки и другой строки типа Li teralString: • sl: typing.LiteralString 'Python' sl + ' 3.13' # # Допустимо s2: typing.LiteralString s3: typing.LiteralString obj.get string() # Недопустимо Допустимо указывается у метода, возвращающего ссылку на текущий объект: Sel f - class Cls: def method(self) -> typing.Self: return self • одно из заданных значений: Literal - <значение Literal[ 1>, 2>, . . . , <значение <значение N>] Пример: Переменная, # способная хранить только строки 'Python', 'JS' или # 'Java' lang_name: typing.Literal['Python', • Never или NoReturn - 'JS, 'Java'] = 'Python' указывается в качестве типа возвращаемого результата у функции (метода), которая не должна возвращать никакого результата, даже None (например, у функции, возбуждающей какое-либо исключение): def func4() -> typing.Never: raise SomeError • Any -- значение любого типа. Эквивалентно отсутствию аннотации типа: а: typing .Any 15.1.1.2. = " Указание типов элементов у списков и кортежей Аннотация типа для элементов списка записывается следующим образом: list[ <тип элементов>] Примеры: # Переменная, способная хранить списки 11: list[str] = ['Python', 'Java'] # Переменная, # типа способная хранить списки строк строк и последовательностей байтов bytes 12: list[str I bytes] = ['Python', b'Java'] Аннотация типов для элементов кортежа ограниченного размера записывается в формате: tuple[ <тип элемента 1>, <тип элемента 2>, . . . , <тип элемента N>]
234 Часть 11. Язык Python: расширенные инструменты Примеры: # Переменная, способная хранить кортеж из трех элементов: # строки и логической величины tpl: tuple(int, str, bool] ( 1, 'аЬс', False) tpl ( 1, False, 'аЬс') tpl ( 1, 'аЬс', False, 3. 9) целого числа, # # # Допустимо Недопустимо Недопустимо from datetime import date # Переменная, способная хранить кортеж из трех элементов: # строки или последовательнсти байтов типа bytes и объекта класса date tp2: tuple[str I bytes, date] ('аЬс', date(2025,8,18)) # Допустимо tp2 (b'wv', date(2025,7,14)) # Допустимо tp2 (b'wv', 14)) # Недопустимо Аннотация типа для элементов кортежа неограниченной длины записывается в формате: tuple[ <тип элементов>, ... ] Примеры: # Переменная, способная хранить кортеж # целочисленных элементов tp3: tuple [int, ... ] = (1, 2, 3) # Переменная, способная хранить # целых и вещественных чисел tp4: tuple[int I float, ... ] = из произвольного количества кортеж из произвольного количества (1, 2.4, 3, 78.09) from typing import Any # Переменная, способная хранить кортеж из # значений любых типов tp5: tuple[Any, ... ] = (1, 2.4, 3, 78.09, произвольного количества 'аЬс', date(2025, 8, 18)) Указание типов элементов у последовательностей и отображений произвольных типов 15.1.1.3. Также имеется возможность указать тип значений у элементов любых объектов, имеющих функциональность последовательности и отображения. Для этого приме­ няются два класса из модуля collections. аЬс стандартной библиотеки: ♦ Sequence [ <тип> ] - последовательность, содержащая элементы заданного типа: from collections.abc import Sequence # Переменная, способная хранить # со строками sql: Sequence [ str] = ( произвольные последовательности 'Python', 'Java') # Переменная, способная хранить произвольные последовательности # со строками и последовательностями байтов типа bytes sq2: Sequence [ str I bytes] = ( 'Python', Ь' Java')
Урок 15. 235 Аннотации типов и документирование кода. Датаклассы ♦ Mapping - Mapping[ отображение, содержащее ключи и элементы заданных типов: <тип ключей>, <тип значений>] Пример: from collections.abc import Mapping # Переменная, способная хранить отображения с элементами, # которые имеют строковые ключи и значения mpl: Mapping[str, str] = {'server': 'Django', 'client': 'React') # Переменная, способная хранить отображения с элементами, которые # имеют строковые ключи и целочисленные или вещественные значения mp2: Mapping[str, int I float] = {'nШТtl': 4.38, 'num2': 22) 15.1.1.4. Указание типов у функций, присваиваемьIх переменным Если переменная (атрибут) предназначена для хранения ссылки на функцию (ме­ тод), то можно указать типы параметров и возвращаемого результата функций, присваиваемых этой переменной (атрибуту). Для этого применяется класс callaЫe ИЗ модуля CallaЫe collections. аЬс: [ [ <тип параметра 1>, <тип параметра 2>, ... , <тип парам. N> ] , <тип результата>) Пример: from collections.abc import CallaЫe def funcl(s: str, i: int) -> bool: # Хранит функцию со строковым и целочисленным # логическим результатом fl: CallaЫe[[str, int], bool) = funcl параметрами и def func2(s: str, i: float) -> bool: #Тоже самое, только второй параметр функции может # либо вещественным числом f2: CallaЫe[[str, int I float], bool] f2 15.1.1.5. быть либо целым, func2 funcl Указание типов у фьючеров Для задания типа у значения, выдаваемого фьючером (см.разд. ся класс AwaitaЫe из модуля AwaitaЫe[ <тип значения>] collections. аЬс: 14.1.1), использует­
236 Часть 11. Язык Python: расширенные инструменты Пример: from collections.abc import AwaitaЫe # Сопрограмма, возвращающая фьючер, который async def corol() -> AwaitaЫe[int]: ftl: AwaitaЫe[int] corol() = # Сопрограмма, возвращающая фьючер со async def coro2() -> AwaitaЬle[str]: ft2: AwaitaЫe[str] выдает целое число строкой coro2() = # Сопрограмма, возвращающая пустой фьючер async def соrоЗ() -> AwaitaЫe[None]: ftЗ: AwaitaЫe[None] 15.1.1.6. Указание = соrоЗ() типов значений, выдаваемых генераторами-функциями Чтобы задать тип значений, выдаваемых генераторами-функциями, применяются следующие классы из модуля ♦ Generator - collections. аЬс: указывает тип значения, возвращаемого обычным генератором­ функцией: Generator[ <тип значения>] Пример: from collections.abc import Generator # Генератор-функция, выдающий целые числа def genl() -> Generator[int]: yield int_value ♦ Iterator - то же самое, что и Generator; ♦ AsyncGenerator - указывает тип значения, возвращаемого генератором-сопро­ граммой: AsyncGenerator[ <тип значения>] Пример: from collections.abc import AsyncGenerator # Генератор-сопрограмма, выдающий строки async def gen2() -> AsyncGenerator[str]: yield str value ♦ Asynciterator - то же самое, что и AsyncGenerator.
Урок 15. 15.1.1.7. Обобщенные типы тип, не задаваемый в исходном коде, а определяемый Обобщенный тип данных 11 237 Аннотации типов и документирование кода. Датаклассы в процессе его выполнения. Обобщенные типы могут быть использованы в объявлении функции или класса. Каждый обобщенный тип, применяемый в объявлении, должен иметь уникальное имя. В качестве имен обобщенных типов традиционно используют буквы латини­ цы, набранные в верхнем регистре, начиная с «Т» (т, u, v и т. д.). Перечень имен обобщенных типов, применяемых в объявлении, указывается в сле­ дующем формате: [ <имя типа 1>, <имя 2>, . . . , типа <имя типа N>] Указывается этот перечень: ♦ в объявлении функции def func[T, U] return (а, (а: Ь: Т, между ее именем и круглыми скобками: - U) -> tuple(T, U): Ь) Здесь тип т соответствует типу значения, полученного функцией func () с пер­ вым параметром, а тип u- типу значения второго параметра. Функция должна вернуть кортеж из двух элементов: значения типа т и значения типа ♦ в объявлении класса u; между его именем и конечным двоеточием или, у произ­ - водных классов, между именем и круглыми скобками: class Clsl [Т]: def method(self, return а class Cls2 15.1.2. [Т] а: Т) -> Т: (BaseClass): Получение аннотаций, заданных у функций и методов Аннотацию, указанную у функции или метода класса, можно получить вызовом функции get _annotations () из модуля inspect стандартной библиотеки: get_annotations(<oбьeкт, представляющий объявление функции или метода>) Функция возвращает аннотацию в виде словаря, в котором ключи элементов пред­ ставляют собой имена параметров, а значения - ссылки на классы, соответствую­ щие заданным типам. Тип возвращаемого результата хранится под ключом >>> from inspect import get_annotations >>> def funcl(a: int, Ь: str) -> str: return Ь >>> get annotations(funcl) 'Ь': <class 'str'>, 'return': <class 'str'>} {'а': <class 'int'>, return:
Часть 238 >>> >>> def func2[T] return а (а: Т) -> //. Язык Python: расширенные инструменты Т: >>> get_annotations(func2) { 'а' : Т, 'return' : Т 1 >>> >>> class Clsl: def rnethod(self, а: int) -> int: return а >>> get_annotations(Clsl.rnethod) {'а': <class 'int'>, 'return': <class 'int'>I Строки документирования 15.2. Строка документирования (одинарных - строка, созданная с применением тройных кавычек или двойных) и не присвоенная ни одной переменной. Служит для напи­ сания документации к программному коду. Строка документирования может быть указана: ♦ в обычном коде для документирования самого модуля, в котором она присут­ - ствует: '''Генератор def class ♦ графических миниатюр''' thшnЬnailer( ... ) : ThшnЬnailer: в объявлении функции, непосредственно после первой строки с ключевым сло­ вом def, - def документирует эту функцию: thшnЬnailer(source_file_path, '''Создает миниатюру размерами ♦ dest_folder_path): 32Ох240 пикселов''' в объявлении класса, сразу после первой строки с ключевым словом class, - документирует этот класс: class ThшnЬnailer: '''Создает миниатюры произвольных размеров соответственно заданным параметрам''' ♦ в объявлении метода, непосредственно после первой строки с ключевым словом def, -документирует этот метод: class ThшnЬnailer:
Урок Аннотации типов и документирование кода. Датаклассы 15. 239 def set_size(width, height): ' ' 'Задает размеры миниатюр' ' ' Получить строку документирования можно, обратившись к атрибуту _ doc _ объ­ екта модуля, объявления функции, класса или метода. Напишем небольшую программу с модулем, содержащим объявления функции и класса с одним методом, и стартовым модулем, который импортирует первый модуль и выводит имеющиеся в нем строки документирования. Создадим в произвольном месте файловой системы папку для хранения кода про­ граммы, дав ей произвольное имя. В этой папке создадим папку в нее пустой модуль _init_.py, превратив папку в пакет modules и поместим Python. Сначала создадим модуль с объявлениями, дав ему имя definitions.py и сохранив в папке modules (листинг 15 .1 ). Лмстмнr 11.1. Проrрамма, выводящая строки документирования, модуnь modulel\deflnltlon1.py '''Объявления функции и класса с одним методом''' def addition(a: int I float, Ь: int I float ) -> int '''addition(a, Ь) - выводит сумму заданных чисел float: Параметры: а (int * Ь (int return а+ float) float) - * первое слагаемое; второе слагаемое.''' Ь class Differ: '''Differ - вычисляет разность заданных чисел''' def difference(self, а: int I float, Ь: int I float) -> int I float: '''Differ.difference(a, Ь) - выводит разность заданных чисел Параметры: а (int I float) * Ь (int I float) return а - Ь * Стартовый модуль star1.py уменьшаемое; вычитаемое.''' (листинг 15 .2) поместим непосредственно в папку с ко­ дом программы. J1мстмнr 11.2. Проrрамма, ■ы■одящая строки документирования, стартоаwй модуnа. ttart.py import modules.definitions as definitions print(definitions. doc print () print(definitions.addition. doc
Часть 240 11. Язык Python: расширенные инструменты print () print(definitions.Differ. doc print () print(definitions.Differ.difference. input('Дпя doc завершения программы нажмите <Enter>') После запуска программа выведет следующее: Объявления функции addition(a, Ь) - и класса с одним методом выводит сумму заданных чисел Параметры: * а (int * Ь (int Differ - float) float) - первое слагаемое; второе слагаемое. вычисляет разность Differ.difference(a, Ь) - заданных чисел выводит разность заданных чисел Параметры: * а * Ь уменьшаемое; (int float) (int I float) - вычитаемое. Строку документирования, находящуюся в заданной функции, классе или методе, можно вывести также с помощью встроенной функции help () : hеlр(<обьект модуля, объявления функции, класса или метода>) При этом будет выведена и первая строка объявления заданной функции, класса или метода, содержащая ключевое слово Например, если в стартовом def модуле или class. написанной ранее программы (см. лис­ тинг 15.2) записать выражение: help(definitions.addition) программа выведет: Help on fuпction addition in module modules.definitions: addition(a: int I float, Ь: int I float) -> int I float addition(a, Ь) - выводит сумму заданных чисел Параметры: * а * Ь (int (int float) float) - первое слагаемое; второе слагаемое. Если в вызове функции help () задать объект модуля, то будут выведены все строки документирования, указанные во всех функциях, объявлены в этом модуле. классах и методах, которые
Урок 15. Аннотации типов и документирование кода. Датаклассы 241 15.3. Датаклассы Датакласс - класс, позволяющий хранить строго определенный набор значений строго определенных типов под заданными именами и предоставляющий удобный набор инструментов для работы с этими значениями. Датакласс уже содержит функциональность, позволяющую: ♦ заносить значения при создании объекта этого класса, задавая их в позиционных или именованных параметрах конструктора; ♦ преобразовываться в строки, содержащие перечни хранящихся значений; ♦ сравнивать наборы значений, хранящихся в разных объектах этого класса. Можно сказать, что датакласс- это более развитая (но и отнимающая больше сис­ темных ресурсов) альтернатива именованному кортежу (см.разд. 5. 2. 3). При объявлении датаклассов активно используются аннотации типов, поэтому раз­ дел о датаклассах и находится в этой главе. 15.3.1. Простейшие датаклассы Датакласс объявляется так же, как и обычный класс, только обязательно декориру­ ется декоратором dataclass () из модуля dataclasses. В классе объявляются классо­ вые атрибуты, описывающие сохраняемые значения, и у каждого такого атрибута указывается аннотация типа. Пример простейшего датакласса Point, описывающего экранный пиксел и анало­ гичного одноименному именованному кортежу (см. разд. 5.2.3): >>> frorn dataclasses irnport dataclass >>> Объявляем датакласс, декорируя его декоратором dataclass() >>> @dataclass class Point: # Объявляем классовые атрибуты, описывающие хранящиеся в датаклассе # значения, не забыв задать у них аннотации необходимых типов х: int у: int color: int is visiЫe: bool Занести значения в объект датакласса можно непосредственно при его создании, в вызове автоматически создаваемого конструктора. Указать заносимые значения можно как в позиционных параметрах конструктора (первый параметр соответст­ вует первому атрибуту из объявленных в датаклассе, второй параметр - атрибуту и т. д.), так и в именованных. Примеры: >>> Создаем объект датакласса, задав сохраняемые >>> параметрах конструктора >>> pointl = Point(400, 300, ОхОООООО, False) значения в позиционных второму
Часть 242 >>> Создаем другой объект, задав >>> point2 = Point(y=400, х=300, 11. значения Язык Python: расширенные инструменты в именованных параметрах is_visiЬle=True, color=0x33ff44) Значения, хранящиеся в объекте датакласса, доступны через атрибуты, также соз­ даваемые автоматически: >>> pointl.y 300 Значения можно изменять: >>> point2.x = 200 >>> point2.x 200 Объект датакласса можно преобразовать в строку (поскольку в датакласс автомати­ чески добавляется dunder-мeтoд _ str_ () - см. разд. 1О. 2. ]). Такая строка содер­ жит имя датакласса, на основе которого был создан объект, и набор хранящихся в нем значений: >>> str(point2) 'Point(x=200, у=400, color=3407684, is_visiЫe=True)' >>> print(pointl) Point(x=400, у=300, color=0, is_visiЫe=False) Объекты датакласса можно сравнивать друг с другом с помощью операторов и ! = (поскольку в датакласс автоматически добавляется dunder-мeтoд см. разд. 1О. 2. 3). _ == eq_ () - Объекты считаются равными, если хранят одинаковые наборы значений: >>> point3 = Point (400, 300, ОхОООООО, False) >>> pointl == point3, pointl point2 (True, False) >>> pointl != point3, pointl != point2 (False, True) При объявлении датакласса у классовых атрибутов можно задать значения по умолчанию, записав их после знака равенства: >>> @dataclass class Point2: х: int у: int # У атрибутов color и is color: int = ОхОООООО is visiЫe: bool = True visiЫe указываем значения >>> point3 = Point2(x=100, у=200) >>> print(point3) Point2(x=100, у=200, color=0, по умолчанию is_visiЫe=True) В качестве значений по умолчанию у атрибутов датакласса можно указать лишь зна­ 11 чения неизменяемых типов: числа, строки, логические величины, кортежи, неизме-
Урок 15. Аннотации типов и документирование кода. Датаклвссы 243 няемые множества и последовательности байтов типа bytes. Попытка задать значение изменяемого типа (например, список, словарь или обычное множество) приведет к исключению типа valueError. Если у какого-либо атрибута датакласса требуется задать в качестве значения по умолчанию величину изменяемого типа, следует использовать функцию field () из модуля dataclasses, передав ей с именованным параметром default_factory функ­ цию, возвращающую необходимое значение, или просто ссылку на соответствую­ щий класс: frorn dataclasses irnport field @dataclass class DataCls: # Атрибут items датакласса получит в качестве # пустой список iterns: list = field(default_factory=list) 15.3.2. значения по умолчанию Более сложные датаклассы У объявляемого датакласса можно указать как сам декоратор dataclass (), так и его вызов. В последнем случае функция dataclass () вернет декоратор, настроенный соответственно заданным при вызове параметрам, который и будет применен к объявляемому классу. Фактически функция dataclass () в этом случае выступит в качестве в разд. фабрики функций-декораторов (о фабриках функций рассказано 6.5.1). Формат вызова функции dataclass () таков: dataclass ( [ ini t=True) [, ) [ eq=True] [, ) [ order=False) [, ] [ frozen=False) [, [kw_only=False)) Параметры функции: ♦ если тrue, декоратор добавит в датакласс конструктор, если False - init - не добавит (тогда конструктор придется объявить самостоятельно); ♦ eq - если тrue, декоратор добавит dunder-мeтoд если False - не доба­ если тrue, декоратором будут добавлены dunder-мeтoды _ lt _ (), _ eq_ (), вит (тогда его следует объявить самостоятельно); ♦ order - _ le_ (), _gt _ () и _ge _ () (см.разд. ♦ frozen - 10.2. 3), если False - не будут добавлены; если True, значения в объекте датакласса после его создания нельзя будет изменить, и попытка сделать это приведет к исключению типа TypeError. Если False, значения в объекте будут доступны для изменения; ♦ kw_ only - если тrue, при создании объекта датакласса значения могут быть ука­ заны лишь в именованных параметрах конструктора, если ционных, так и в именованных параметрах. False - как в пози­
Часть 244 1/. Язык Python: расширенные инструменты Пример: # Указываем, в частности, что в датаклассе # dunder-мeтoд _eq_() @dataclass(eq=False, kw_only=True) не нужно создавать class Point: # Объявляем в датаклассе dunder-мeтoд _eq_(), # лишь значения атрибутов х и у def _eq_(self, n): return self.x == n.x and self.y == n.y Функция field (), упоминавшаяся в разд. 15.3.1, который сравнивает позволяет задать довольно много параметров у атрибута датакласса. Вот формат ее вызова: field([default=dataclasses.MISSING] [, ] [default factory=dataclasses.MISSING] [,] [init=True] [, [repr=True] [, ] [cornpare=True] [, ] [kw_only=False]) Вот поддерживаемые параметры: ♦ default - значение атрибута по умолчанию. Если указано значение переменной MISSING из модуля dataclasses, у атрибута не будет значения по умолчанию; ♦ default_factory - функция, возвращающая значение атрибута по умолчанию, или ссылка на требуемый класс. Заданная функция (конструктор указанного класса) не должна принимать параметров. Применяется для задания значения по умолчанию, относящегося к изменяемым типам (см. ранее приведенный при­ мер). Если указано значение переменной MISSING из модуля dataclasses, у атри­ бута не будет значения по умолчанию; ♦ ini t - если тrue, значение атрибуту можно будет дать как при создании объек­ та датакласса, так и после его создания, если False - лишь после создания объ­ екта; ♦ repr - если тrue, значение атрибута будет включаться в состав строки, которая возвращается автоматически dunder-мeтoдoм создаваемым _ str_ () , если False- не будет включаться; ♦ cornpare - если True, значение атрибута будет участвовать в сравнении объектов автоматически создаваемым dunder-мeтoдoм ♦ kw_ only - _ eq_ (), если False - не будет; если True, при создании объекта датакласса значение атрибута может быть указано лишь в именованном параметре конструктора, если False в позиционном, так и в именованном параметре. Пример: @dataclass class Point: х: int у: int как
Урок 15. Аннотации типов и документирование кода. Датаклассы 245 # Этот атрибут получит значение по умолчанию ОхОООООО # и не будет задействован в операциях сравнения color: int = field(default=OxOOOOOO, compare=False) # # А этот атрибут также не будет присутствовать выдаваемой автоматически добавленным методом is_visiЫe: bool = field(default=True, в составе str строки, () repr=False, compare=False) Наконец, в датаклассе можно объявить любые произвольные методы: from math import sqrt @dataclass class Point: def hypot(self): return sqrt(self.x ** 2 + self.y ** 2) 15.4. Что еще нужно знать об аннотациях типов Как говорилось ранее, аннотации типов полностью игнорируются исполняющей средой. Так, следующий код выполнится без каких бы то ни было ошибок: >>> def addition(a: int, return а+ Ь >>> addition('Pyt', 'Python' Ь: int) -> int: 'hon') Однако аннотации типов используются различными сторонними программами, обрабатывающими программный код (например, проверяющими его на наличие ошибок), и некоторыми дополнительными библиотеками. 15.5. Самостоятельное упражнение Исправьте тестовую программу, выводящую строки документирования (см. лис­ тинги 15.1-15.2), таким образом, чтобы она выводила строки документирования, присутствующие в самом модуле, с помощью функции help ( J, и посмотрите, что она выведет при запуске.

ЧАСТЬ 111 Практическое Руthоn-проrраммирование :> Урок 16. Загрузка и анализ данных из Интернета :> Урок 17. Разработка веб-приложений, часть :> Урок 18. Работа с базами данных :> Урок 19. Разработка веб-приложений, часть :> Урок 20. Разработка графических приложений :> Урок 21. Математика :> Урок 22. Искусственный интеллект 1 2

Урок16 Загрузка и анализ данных из Интернета Python часто используется для загрузки и парсинrа веб-страниц. Парсинг 11 разбор документа (например, веб-страницы) с целью определить его структуру и извлечь содержимое. Для этого применяется несколько дополнительных библиотек, описываемых далее. 16.1. Библиотека requests: загрузка файлов из Интернета Для загрузки файлов (например, веб-страниц) из Интернета удобно использовать дополнительную библиотеку requests. Установить версию 2.32.* этой библиотеки, описываемую в книге, можно подачей команды: pip install "requests==2.32" На заметку Полная документация по библиотеке requests находится по интернет-адресу: https://requests.readthedocs.io/. 16.1.1. Requests: отправка клиентских запросов Чтобы загрузить какой-либо файл из Интернета, следует отправить клиентский за­ прос. Отправка запроса на загрузку файла, находящегося по заданному интернет­ адресу, выполняется вызовом функции get () из модуля requests: get (<интернет-адрес> [, params=None] [, headers=None] [, timeout=None] [, stream=False] ) Интернет-адрес загружаемого файла задается в виде строки. Остальные параметры: ♦ params - GЕТ-параметры, помещаемые в состав интернет-адреса отправляемого запроса. Указываются в виде: • словаря ключи элементов станут именами GЕТ-параметров, а значения - элементов - значениями GЕТ-параметров;
Часть 250 111. Практическое Руthоп-программирование списка кортежей, содержащих по два элемента, • вложенного кортежа станет именем второй элемент - - первый элемент каждого соответствующего GЕТ-параметра, а его значением. Если задано значение None, никакие GЕТ-параметры в отправляемый запрос до­ бавлены не будут; ♦ дополнительные заголовки, добавляемые в отправляемый запрос. headers - Указываются в тех же форматах, что и GЕТ-параметры; ♦ время, в течение которого библиотека будет ожидать ответа от серве­ timeout - ра, в виде вещественного числа в секундах. Если указано значение None, будет использован промежуток времени по умолчанию, задаваемый операционной системой (обычно ♦ 45 секунд); stream-paccмoтpeн в разд. Функция 16.1.3. get () отправляет запрос, дожидается ответа от сервера и возвращает Response из модуля requests, представляющий полученный ответ. объ­ ект класса В процессе отправки запроса могут быть возбуждены следующие исключения, объ­ явленные в том же модуле ♦ ConnectionError - ♦ HTTPError - requests: не удалось соединиться с сервером; сервер вернул сообщение об ошибке (ответ с кодом, отличным от 2ххи зхх); ♦ Timeout - истекло время ожидания ответа. Все эти исключения являются производными от класса исключения RequestException. Для примера попробуем в интерактивной оболочке загрузить файл главной страни­ цы сайта 3DNews.ru 1: >>> from requests import get >>> r = get('https://Зdnews.ru/') >>>#Посмотрим на объект серверного ответа, возвращенный функцией get() >>> r <Response [200]> Как видим, в выведенной строке, описывающей объект ответа, присутствует код статуса 200 - значит, загрузка файла увенчалась успехом. Загрузим главную страницу русской редакции поисковика Google с результатами поиска по слову «Python». Нам потребуется отправить запрос по интернет-адресу https://www.google.ru/search нем q и значением 'Python': и передать в составе запроса GЕТ-параметр с име­ >>> r = get('https://www.google.ru/search', params={'q': 'Python')) >>> r <Response [200]> И в этом случае файл страницы бьm успешно загружен. 1 https://Зdnews.ru/.
Урок 16. Загрузка и анализ данных из Интернета 251 16.1.2. Requests: обработка полученных серверных ответов Как говорилось ранее, функция get ( ) возвращает полученный серверный ответ в виде объекта класса Response из модуля requests. Этот объект содержит загру­ женный файл. Для получения содержимого загруженного файла нужно использовать следующие свойства класса ♦ Response: содержимое загруженного текстового файла (например, веб-страницы) text - в виде строки; ♦ содержимое загруженного двоичного файла content - (скажем, графического изображения) в виде последовательности байтов типа bytes; ♦ True, если запрос был успешно обработан (был получен ответ с кодом ста- ok - туса 2хх или зхх), и False Метод класса Response декодирует содержимое ответа, j son () в формате в противном случае. JSON, представленное и возвращает результат в виде словаря или списка в зависимо­ - сти от формата закодированных данных. Могут оказаться полезными следующие атрибуты класса Response: ♦ status_code - ♦ reason - ♦ headers - код статуса ответа в виде целого числа; текстовое описание статуса ответа; заголовки полученного ответа в виде словаря; ♦ encoding - обозначение текстовой кодировки, в которой набран текст в загру- женном текстовом файле, в виде строки. Для примера загрузим главную страницу сайта 3DNews.ru и просмотрим сведения о полученном ответе: >>> r = get('https://Зdnews.ru/') >>> r.ok True >>> r.encoding 'utf-8' >>> r.status_code, r.reason (200, 'ОК') Сохраним загруженную страницу в файле c:\work\downloaded\Зdnews.ru.html: >>> f = open(r'c:/work/downloaded/Зdnews.ru.htrnl', rnode='w', encoding='utf-8') >>> f.write(r.text) 240581 >>> f.close() Вызовом встроенной функции open () мы открываем файл для записи. Этой функ­ ции первым параметром передаем путь к файлу, параметром rnode - режим откры-
Часть 252 тия (строка 'w' - 111. Практическое Руthоп-программирование открытие в текстовом режиме для записи, если файл не сущест­ вует, он будет создан автоматически), а параметром encoding - строковое обозна­ чение текстовой кодировки. Функция возвращает объект, представляющий откры­ тый файл. Запись в файл выполняем вызовом у него метода write(), которому передаем текстовое содержимое ответа и который возвращает количество записан­ ных байтов. После чего закрываем файл вызовом метода close (). Проверим, сохранилась ли загруженная веб-страница, и откроем ее в веб-обо­ зревателе. Аналогичным образом загрузим и сохраним в файле c:\work\downloaded\logo.png лого­ хранящийся по интернет-адресу https://Зdnews.ru/assets/ тип сайта ЗDNews.ru, images/logo.png: >>> r = get('https://Зdnews.ru/assets/irnages/logo.png') >>> f = open(r'c:/work/downloaded/logo.png', rnode='wb') >>> f.write(r.content) 14 981 >>> f.close() Здесь в вызове функции open ( ) мы указываем режим открытия файла 'wb' ( запись в двоичном режиме) и не задаем текстовую кодировку (которая при двоичном ре­ жиме открытия не нужна). Посмотрим, загрузился ли файл с логотипом. Библиотека requests - отличный инструмент для загрузки разнообразных файлов из Интернета. Однако следует помнить, что загрузка с ее помощью нескольких файлов будет выполняться строго последовательно, что снизит производитель­ ность. Хотя, безусловно, можно выполнять загрузку в нескольких потоках или про­ цессах (см. урок 13) или aiohttp (см.разд. 16.3). 16.1.3. Requests: использовать аналогичную, но конкурентную библиотеку загрузка больших файлов По умолчанию функция get () загружает и сохраняет в памяти запрошенный файл целиком. Если файл очень большой, его сохранение отнимет много памяти. Однако есть возможность загружать и обрабатывать файлы по частям (чанкам)­ это существенно сэкономит память. Для этого следует в вызове функции get () дать параметру strearn значение True. Для загрузки и считывания содержимого файла по частям применяется метод iter_content ( [chunk_size=l]) класса Response. Параметр chunk_size задает пре­ дельный размер выдаваемых чанков в виде целого числа в байтах. Если ему при­ своить значение None, размер чанка установит библиотека. Метод i ter _ content () возвращает итератор, который выдает раз за разом чанки, составляющие содержимое загружаемого файла и представленные последователь­ ностями байтов типа bytes. Для примера загрузим главную страницу сайта ЗDNews.ru чанками по и сохраним в файле: 128 Кбайт
Урок 16. Загеrзка и анализ данных из Интернета 253 >>> r = get('https://Зdnews.ru/', stream=True) >>> f = open(r'd:/work/!temp/Зdnews.ru.html', mode='ab') >>> for chunk in r.iter_content(chunk_size=l28 * 1024): f.write(chunk) . Вывод >>> f.close() метода write() опущен ... Здесь в вызове функции open () в качестве режима открытия файла задаем строку 'аь' - открытие для дозаписи в двоичном режиме. 16.2. Библиотека Beautiful Soup: парсинг веб-страниц Предположим, что мы загрузили страницу какого-либо сайта и теперь хотим найти в ней все заголовки первого уровня. Для этого нам придется выполнять поиск в НТМL-коде этой страницы с использованием регулярных выражений (см.урок 11), и довольно сложных. Та еще задачка ... Работу существенно упростит дополнительная библиотека вить версию 4.13.* Beautiful Soup. У стано­ этой библиотеки, которая описывается в книге, можно коман­ дой: pip install "beautifulsoup4==4.13" На заметку Полная документация по библиотеке Beautiful Soup https://www.crummy.com/software/BeautifulSoup/. 16.2.1. находится по интернет-адресу: Подготовка к парсингу веб-страницы Чтобы библиотека Beautiful Soup произвела парсинг заданной страницы, следует создать объект парсера. 11 Парсер - программный механизм, выполняющий парсинг. Объект парсера создается на основе класса вeautifulSoup из модуля bs4. Конструк­ тор этого класса вызывается в формате: Beautifu1Soup(<H1МL-кoд веб-страницы>, <обозначение ядра парсера>) НТМL-код задается в виде строки. Для парсинга веб-страниц в качестве обозначения ядра парсера следует указать строку 'html. parser'. Для примера создадим тестовую страницу, записав ее НТМL-код в виде обычной строки Python, и на основе этой страницы парсер: >>> from bs4 import BeautifulSoup >>> html = ' ' '<html> <head>
Часть 254 111. Практическое Руthоп-программироввние <title>Tecтoвaя страница</titlе> </head> <body> <hl>Заголовок <h2>Заголовок l</hl> 2</h2> <р>Абзац 1</р> <р class="clsl">Aбзaц 2</р> <h2>Заголовок 3</h2> <р>Абзац 3</р> </body> </htrnl>' ' ' >>> parser = BeautifulSoup(htrnl, 'htrnl.parser') 16.2.2. Навигация по структуре узлов веб-страницы Beautiful Soup создает в памяти структуру, аналогичную объектной модели (DOM), которая представляет переданную ему страницу. Каждый из присутствующий в коде страницы, представляется тег или текстовый, узлов Парсер документа особым объектом. По этой структуре можно перемещаться, получая доступ к раз­ личным тегам и фрагментам текста. Объект парсера имеет функциональность тега <htrnl>. Обратившись к парсеру, можно получить объект узла, представляющий этот тег. При выводе в консоли объ­ ект узла отображается в виде строки с его содержимым: >>>#Будет выведено содержимое тега <html> >>> parser <htrnl> <head> <title>Tecтoвaя страница</titlе> </head> <body> . . . Часть </body> </htrnl> вывода пропущена ... Объект парсера (не забываем, что он аналогичен тегу <htrnl>) и все объекты узлов содержат следующие атрибуты: ♦ <имя вложенного тега> - объект, представляющий первый встретившийся вло­ женный тег с заданным именем: >>>#Будет выведено содержимое тега >>> parser.head <head> <title>Tecтoвaя страница</titlе> </head> >>> parser.head.title <title>Tecтoвaя страница</titlе> <head>
Урок 16. Загрузка и анализ данных из Интернета 255 >>> body = parser.body >>> body.hl <hl>Заголовок l</hl> >>>#Будет выведен первый встретившийся в НТМL-коде тег <h2> >>> body.h2 <h2>Заголовок ♦ 2</h2> список всех прямых потомков текущего тега, включая текстовые, contents - в том числе и переводы строк в НТМL-коде: >>> body.contents ['\n', <hl>Заголовок l</hl>, '\n', <h2>Заголовок 2</h2>, '\n', <р>Абзац 1</р>, '\n', <р class="clsl">Aбзaц 2</р>, '\n', <h2>Заголовок 3</h2>, '\n', <р>Абзац 3</р>, '\n'] >>>#Узел-тег >>> body.contents[l] <hl>Заголовок l</hl> >>>#Текстовый узел - перевод >>> body.contents[O] строки '\n' ♦ то же самое, что и children - contents, только хранит итератор, а не готовый список: >>> for с if in body.contents: с!= '/n': print (с) <hl>Заголовок <h2>Заголовок <р>Абзац l</hl> 2</h2> 1</р> <р class="clsl">Aбзaц 2</р> <h2>Заголовок 3</h2> <р>Абзац 3</р> ♦ ссьmка на родительский тег или None, если тег не имеет родителя: parent - >>> title = parser.head.title >>> title.parent.name 'head' ♦ parents - итератор, выдающий последовательность, которая содержит всех ро­ дителей текущего узла: >>> for р in title.parents: print(p.name, end=', ') head, html, [document], Последний родитель ♦ - сама страница; self_and_parents -то же самое, что и parents, только включает и текущий узел; ♦ next_siЫing нет; следующий сосед текущего узла или None, если соседей больше
Часть 256 ♦ 111. Практическое Руthоп-программирование предыдущий сосед текущего узла или None, если соседей previous siЫing больше нет; ♦ next _ siЫings - итератор, выдающий последовательность следующих соседей текущего узла: >>> р = body.contents[7] >>> for s in p.next siЫings: if s != '/n': print(s) <h2>Заголовок 3</h2> <р>Абзац 3</р> ♦ previous_siЫings - итератор, выдающий последовательность предыдущих со­ седей текущего узла: >>> for s in p.previous if s ! = '/n': siЫings: print(s) <р>Абзац 1</р> <h2>Заголовок <hl>Заголовок 2</h2> 1</hl> Объект тега имеет функциональность итератора, выдающего последовательность с узлами-потомками. Для примера переберем содержимое тега <body> и выведем лишь узлы-теги: >>> for node in body: # Ключевой признак if node.name: print(node) <hl>Заголовок <h2>Заголовок <р>Абзац узла-тега - наличие у него имени 1</hl> 2</h2> 1</р> <р class="clsl">Aбзaц 2</р> <h2>Заголовок 3</h2> <р>Абзац 3</р> 16.2.3. Получение сведений об узле Получить различные сведения об узле позволят следующие атрибуты объекта узла: ♦ string - строка с текстовым содержимым, если текущий узел - текстовый или тег, содержащий единственный текстовый узел, или None, если это тег, содер­ жащий другие теги и несколько текстовых узлов: >>> body.hl.string 'Заголовок 1'
Урок 16. Загеrзка и анализ данных из Интернета 257 >>> print(body.string) None ♦ strings - итератор, выдающий последовательность строк, которые содержатся в текущем теге: >>> list(body.strings) [ '\n' , 'Заголовок l' , '\n' , 'Заголовок 2' , '\n' , 'Абзац 2' , '\n', 'Заголовок 3' , ' \n' , 'Абзац 3' , ♦ stripped_strings - 'Абзац 1' , '\n' , '\n' ] то же самое, что и strings, только выдаваемая последова­ тельность не включает «пустые» текстовые узлы (наподобие переводов строк '\n •): >>> list(body.stripped_strings) ['Заголовок 1', 'Заголовок 2', 'Заголовок 3', 'Абзац 3'] ♦ 'Абзац 1', 'Абзац 2', имя текущего тега в виде строки или None, если текущий узел не является narne тегом: >>> body.contents[5].narne 'р' ♦ attrs - словарь атрибутов текущего узла-тега. Ключи элементов этого словаря совпадают с именами атрибутов тега, а значения элементов представляют собой значения атрибутов тега. Элемент словаря с ключом class хранит список имен стилевых классов, указанных у тега: >>> р >>> р = body.contents[7] <р class="clsl">Aбзaц 2</р> >>> p.attrs { 'class' : [ 'clsl'] 1 >>> p.attrs['class'] [ 'clsl'] >>> p.attrs['class'] [О] 'clsl' Объект узла-тега поддерживает функциональность отображения, содержащего все атрибуты текущего тега: >>> 'class'] [ 'clsl'] >>> р [ 'class'] 'clsl' р[ 16.2.4. [О] Поиск узлов Для поиска узлов, являющихся потомками текущего узла, по заданным признакам следует вызвать метод find _all () объекта узла, использовав формат:
Часть 258 find аll(<имн тега>[, 111. Практическое Руthоп-программирование attrs={}] [, recursive=True] [, stringltext=None] [, lirnit=None] [, <атрибуты>]) Метод возвращает список всех найденных узлов. Также можно вызвать в приведенном формате сам объект узла как обычную функ­ цию. Имя ♦ тега можно задать в виде: строки: >>> body.find_all('p'I [<р>Абзац l</p>, <р >>> body ( 'р' 1 [<р>Абзац l</p>, <р ♦ class="clsl">Aбзaц 2</р>, <р>Абзац 4</р>] class="clsl">Aбзaц 2</р>, <р>Абзац 4</р>] списка имен тегов или словаря, элементы которого имеют ключи в виде имен тегов и хранят значения True: >>> body(['hl', 'h2']) [<hl>Заголовок 1</hl>, <h2>Заголовок 2</h2>, >>> body({'hl': True, 'h2': True}) [<hl>Заголовок 1</hl>, <h2>Заголовок 2</h2>, ♦ <h2>Заголовок 3</h2>] <h2>Заголовок 3</h2>] регулярное выражение. Для примера найдем теги, имена которых состоят из пяти букв: >>> irnport re >>> parser(re.cornpile(r' [a-z] {5}'1) [<title>Tecтoвaя страница</titlе>] ♦ True -будут найдены все узлы-теги: »> body (True) [<hl>Заголовок 1</hl>, <h2>Заголовок <р class="clsl">Aбзaц 2</р>, 2</h2>, <h2>Заголовок <р>Абзац 1</р>, 3</h2>, <р>Абзац 3</р>] Остальные параметры: ♦ attrs - словарь с атрибутами и их значениями, которые должны содержаться в искомых тегах: >>> body('p', attrs={'class': 'clsl'}) [<р class="clsl">Aбзaц 2</р>] >>> body('h2', attrs={'class': 'clsl'}) [] Атрибуты также можно указать в виде обычных именованных параметров: body ( 'р', id=' special') Атрибут тега class следует указывать с помощью именованного параметра class: body('p', class_='clsl')
Урок ♦ 1б. Загрузка и анализ данных из Интернета 259 если True, поиск будет производиться среди непосредственных по­ recursive - томков текущего узла, среди потомков его потомков и т. д., если False - только среди непосредственных потомков; ♦ string или text - текст, который должен содержаться в искомых тегах. Значе­ ние может быть указано в виде: • обычной строки с искомым текстом: >>> body('p', 2') string='Aбзaц [<р class="clsl">Aбзaц 2</р>) >>> body('p', string='2') [] Внимание/ Параметр text является устаревшим. При его указании может появиться предупреж­ дение типа Deprecationwarning (использован устаревший программный инструмент). • списка искомых фрагментов текста или словаря, элементы которого имеют ключи в виде фрагментов текста и хранят значения тrue: >>> body( 'р', string=[ 'Абзац 2', [<р class="clsl">Aбзaц 2</р>, >>> body('p', string=('Aбзaц • <р>Абзац 2': True, [<р class="clsl">Aбзaц 2</р>, 3')) 'Абзац 3</р>] 'Абзац <р>Абзац 3': True)) 3</р>] регулярного выражения: >>> body ( 'h2', string=re. compile ( r' [ 1-2] ') ) [<h2>Заголовок 2</h2>] • тrue -будут найдены все теги, содержащие текст: >>> body(True, string=True) [<hl>Заголовок l</hl>, <h2>Заголовок <р class="clsl">Aбзaц 2</р>, 2</h2>, <h2>Заголовок <р>Абзац 1</р>, 3</h2>, <р>Абзац 4</р>] ♦ limit - количество найденных узлов, которые будут присутствовать в возвра­ щаемом списке, в виде целого числа. Если задать None, в списке будут все най­ денные узлы: >>> body( 'р') [<р>Абзац >>> body( 'р', [<р>Абзац 1</р>, <р class="clsl">Aбзaц 2</р>, <р>Абзац 3</р>] limit=2) 1</р>, <р class="clsl">Aбзaц 2</р>] Метод f ind ( ) объекта узла аналогичен методу f ind_ al 1 ( ) , только возвращает пер­ вый найденный узел или find(<имя тега>[, Пример: >>> body.find('p') <р>Абзац 1</р> None, если подходящего узла не напmось: attrs=(}] [, recursive=True] [, text=None] [, <атрибуты>))
Часть 260 111. Практическое Руthоп-программирование >>> print(body.find('div')) None Поддерживаются также следующие методы: ♦ find_next_siЫings () - ищет следующих соседей текущего узла, удовлетво­ ряющих заданным параметрам, и возвращает список найденных узлов: attrs={)] [, recursive=True] [, text=None) [, limit=None] [, <атрибуты>]) find_next_siЫings(<имя тега>[, Параметры метода аналогичны таковым у метода find _ all (). Примеры: >>> >>> р = body.find('p') p.find_next_siЫings('h2') [<h2>Заголовок >>> p.find_next 3</h2>] siЫings('hl') [] ♦ find_next_siЫing() - ищет первого следующего соседа, удовлетворяющего заданным параметрам. В остальном аналогичен методу find (): >>> p.find_next_siЫing('h2') <h2>Заголовок 3</h2> >>> print{p.find_next siЫing('hl')) None ♦ find_previous siЫings () - аналогичен методу find_ next siЫings (), только ищет предыдущих соседей текущего узла: >>> p.find_previous_siЬlings('hl') [<hl>Заголовок l</hl>] >>> p.find_previous siЫings('header') [] ♦ find_previous _ siЫing () - аналогичен методу find _ next _ siЫing (), только ищет первого предыдущего соседа текущего узла: >>> p.find_previous_siЫing('hl') <hl>Заголовок l</hl> >>> print(p.find_previous None ♦ find_all_next() - siЫing('header')) аналогичен методу find_next_siЫings(), только ищет не только среди следующих соседей, но и их потомков: >>> parser.head.find_next siЫings('p') [] >>> parser.head. find_all_next ( 'р') [<р>Абзац 1</р>, ♦ find_next() - <р class="clsl">Aбзaц 2</р>, <р>Абзац 3</р>] аналогичен методу find_next_siЫing(), только ищет не только среди следующих соседей, но и их потомков: >>> print(parser.head.find_next None siЫing('p'))
Урок 16. 261 Загеrзка и анализ данных из Интернета >>> print(parser.head.find_next('p')) <р>Абзац 1</р> ♦ аналогичен методу - find _all_previous () find_previous_siЫings (), только ищет не только среди следующих соседей, но и их потомков: >>> body.find_previous siЫings('title') [] >>> body.find_all_previous('title') [<title>Tecтoвaя страница</titlе>] ♦ аналогичен методу find_previous _ siЬling (), только ищет не find_previous () - только среди следующих соседей, но и их потомков: >>> print(body.find_previous_siЫing('title')) None >>> body.find_previous('title') <title>Tecтoвaя страница</titlе> ♦ ищет родителей текущего узла, удовлетворяющих заданным find_parents () - параметрам, и возвращает список найденных узлов. Параметры метода анало­ гичны таковым у метода find_all ( J: >>> p.find_parents('body') [<body> <hl>Заголовок l</hl> ... Остальной ... вывод пропущен <р>Абзац 3</р> </body>] >>> p.find_parents(['body', [<body> . . . Остальной </body>, <html> 'html']) вывод пропущен ... <head> <title>Tecтoвaя страница</titlе> </head> <body> . . . Остальной </body> вывод пропущен ... </html>] ♦ find_parent () - ищет первого родителя текущего узла, удовлетворяющего за­ данным параметрам, и возвращает его. В остальном аналогичен методу find (): >>> р. find_parent ( 'body') <body> <hl>Заголовок ... l</hl> Остальной <р>Абзац 3</р> </body> вывод пропущен ...
262 ♦ Часть css. select ( <СSS-селектор>) - Практическое Руthоп-программирование 111. ищет всех потомков, соответствующих заданно­ му в виде строки сss-селектору, и возвращает список найденных узлов: >>> body.css.select('p:nth-of-type(З) ') [<р>Абзац 3</р>] 16.2.5. Программа, выводящая заголовки статей на веб-сайте ЗDNews.ru Чтобы попрактиковаться в парсинге страниц, напишем программу, которая выво­ дит в консоли заголовки статей, опубликованных на сайте содержание и интернет-адреса (листинг 16.1 ). сведения о статьях из верхнего блока страницы 3DNews.ru, их краткое Для простоты будем выводить лишь - с заголовком Главное. Листииr 16.1. Программа, выводя&ЦаJJ O,,_Tblf ((сайта frorn requests irnport get frorn bs4 irnport BeautifulSoup рrint('Заголовки статей на ЗDNews.ru') print () url = 'https://Зdnews.ru/' # 1 r = get(url) # 2 if r.ok: # 3 page_htrnl = r.text # 4 parser = BeautifulSoup(page_htrnl, 'htrnl.parser') # 5 rnain_Ыock = parser.body.find('div', id='news') # 6 if rnain Ыосk: # 7 rnain_Ыock_news = rnain_Ыock('div', # 8 class ='content-Ыock-data white'} for rnain Ыосk - n in rnain - Ыосk - news: # 9 n title 1 = rnain Ыосk n.css.select\ # 10 ('div.header.rnainph а') [О] n_title_2 = n_title_l.contents[-1] .string # 11 print(n_title_2.strip() .upper()) # 12 teaser_div = rnain_Ыock_n.css.select('div.teaser') [О) # 13 print(teaser_div.string.strip()) # 14 а = rnain Ыосk n.a # 15 print(f'{url[:-l)}{a['href')}') # 16 print() # 17 input ( 'Дпя завершения программы нажмите Сохраняем интернет-адрес сайта рать его каждый раз (поз. 1в <Enter>') 3DNews.ru 16.1 ). листинге в переменной, чтобы потом не наби­
Урок 1б. Загрузка и анализ данных из Интернета 263 Загружаем главную страницу сайта средствами библиотеки загрузка увенчалась успехом (поз. даем на его основе НТМL-парсер 3), извлекаем НТМL-код Beautiful Soap (поз. 5). Верхний блок Главное на главной странице сайта requests (поз. страницы (поз. 3DNews.ru 2). Если 4) и соз­ помещается в блоке (блочном контейнере <div>) с якорем news (якорь задается атрибутом тега id). Ищем этот блок в секции тела страницы (в теге <body> ден ли он (поз. поз. 6) и проверяем, най­ 7). Отдельные новости в блоке Главное размещаются в блоках со стилевыми классами content-Ыock-data и white. Ищем все эти блоки (поз. с ними (поз. 8) и перебираем список 9). Чтобы извлечь текст заголовка новости, придется потрудиться. Сначала в очеред­ ном блоке с новостью ищем гиперссылку (тег <а>), вложенную в блок со стилевыми классами header и rnainph (поз. 1О). Не забываем, что метод css. select () возвращает список найденных тегов, даже если найденный тег всего один. Далее извлекаем по­ следнего (поз. 11). потомка этой гиперссылки Это текстовое содержимое и - получаем его текстовое собственно текст заголовка содержимое - выводим в консоли, предварительно обрезав начальные и конечные пробелы и преобразовав к верхнему регистру, чтобы сделать заметнее (поз. 12). Краткое содержание новости хранится в блоке со стилевым классом teaser, нахо­ дящемся в блоке с новостью. Ищем этот блок (поз. 13) и выводим его текстовое содержимое, также обрезав у него начальные и конечные пробелы (поз. 14). Интернет-адрес новости можно извлечь из первой гиперссылки, имеющейся в бло­ ке с новостью (поз. 15). При выводе интернет-адреса из этой гиперссылки в консо­ ли учтем, что в атрибуте href тега <а> записан сокращенный интернет-адрес, не содержащий адрес хоста, и нам необходимо преобразовать его в полный. Полный интернет-адрес составляем в переменной (поз. 1), ной гиперссылки (поз. из интернет-адреса, который мы ранее сохранили без последнего символа слеша и интернет-адреса из найден­ 16). Выведя сведения об очередной статье, выводим пустую строку, чтобы отделить их от сведений о следующей статье (поз. 17). Запустим программу и посмотрим, что она выведет. 16.2.6. Программа, сохраняющая изображения с заданной веб-страницы, версия 1.0 Продолжим практические занятия написанием программы, которая загружает фай­ лы с изображениями, опубликованными на странице с заданным интернет-адресом, и сохраняет их в папке с указанным путем (листинг 16.2). Команда на запуск нашей программы будет иметь следующий формат: <имя файла с программой> <интернет-адрес веб-страницы> ~ <путь к папке для сохранения изображений>
Часть///. Практическое Руthоп-программирование 264 Кроме того, ( 1024 программа не будет сохранять файлы размером менее 1 Кбайт байта), поскольку в таких файлах, скорее всего, хранятся элементы оформле­ ния страницы, нам не нужные. версия с from from from from from argparse import ArgumentParser requests import get bs4 import BeautifulSoup urllib.parse import urlparse, urljoin os.path import join print('Coxpaнeниe изображений') ар= ArgumentParser(description='Coxpaнeниe изображений ' # 1 'с заданной веб-страницы') ap.add_argument('url', hеlр='Интернет-адрес веб-страницы', mеtаvаr='исходная-страница') ap.add_argument('destination', hеlр='Путь к папке для 'изображений' сохранения' , mеtаvаr='папка-назначения') р = ap.parse_args() page_url_dict page_base_url urlparse(p.url) f'{page_url_dict.scheme)://{page url dict.netlocl' def do_work(): page_r = get(p.url) if page_r.ok: page_html = page_r.text parser = BeautifulSoup(page_html, 'html.parser') img_tags = parser. body ( 'img' ) for img tag in img_tags: img_url = img_tag['src'] img_url_obj = urlparse(img_url) if img_url_obj.scheme 1 = 'data': if not img_url_obj.scheme or not img_url_obj.netloc: img_url = urljoin(page_base_url, img url) img_r = get(img_url) if img r.ok: file_content = img_r.content if len(file content) > 1024: file_name = img_url obj.path.split(sep='/')\ (-1] f = open(join(p.destination, file_name), mode='wb') f.write(file_content) f. close () # # # # # # # # # # # # # # # # # # 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 20 # 21 # 22 # 23 1.0
Урок 1б. 265 Загрузка и анализ данных из Интернета else: print(f'Фaйл (img_url} не удалось # 24 # 25 загрузить'} do_work() завершения input('ДJiя объект создаем Сначала <Enter>') программы нажмите командных обработчика ArgumentParser из модуля argparse (см.разд. чи (поз. 12. 4) ключей класса основе на и обрабатываем командные клю­ 1 в листинге 16.2). Следует предусмотреть вариант, при котором в теге <img> будет находиться не полный интернет-адрес https://somesite.ru/images/imgl.jpg, бражение не получится. Чтобы а изображением, с файла а сокращенный /images/imgl.jpg), получить (не, скажем, из-за чего загрузить изо­ полный интернет-адрес, необходимо добавить к сокращенному интернет-адресу обозначение протокола и адрес хоста (в нашем примере соответственно выяснить https и somesite.ru). Которые еще необходимо ... Сделать это поможет функция urlparse () из модуля urllib.parse стандартной биб­ лиотеки. принимает Она с параметром интернет-адрес полный и возвращает объект, содержащий его отдельные части. Обозначение протокола хранится в атри­ буте scheme этого объекта, адрес хоста - в атрибуте netloc, а путь к файлу - в ат­ рибуте path. Мы «пропускаем» интернет-адрес страницы, полученный с командным ключом, через функцию urlparse () (поз. 2) и на основе сведений из возвращенного ею объ­ екта составляем строку, представляющую собой полный адрес хоста с обозначени­ ем протокола (поз. 3). Код, загружающий do _ work () (поз. и сохраняющий изображения, оформляем в виде функции чтобы потом без проблем измерить производительность программы 4). В теле этой функции загружаем страницу (поз. грузилась (поз. 6), 5), проверяем, успешно ли она за­ извлекаем из полученного ответа НТМL-код страницы и создаем Beautiful Soap (поз. 7). С помощью парсера <img> (поз. 9) и перебираем их в цикле (поз. 10). на его основе парсер ницы все теги ищем в теле стра­ В теле цикла извлекаем из атрибута src очередного тега <img> интернет-адрес гра­ фического файла (поз. 11) и передаем его функции urlparse () (поз. Другой вариант, который следует предусмотреть, - 12). наличие в атрибуте src тега <img> не интернет-адреса файла, а самого графического файла, представленного data URL. Признаком data URL служит наличие в нем обозначения протоко­ data, что позволит нам сразу опознать его и не обрабатывать далее (поз. 13). в виде ла Далее проверяем, является ли извлеченный интернет-адрес сокращенным. Как го­ ворилось ранее, признаком сокращенного интернет-адреса является отсутствие в нем обозначения протокола и адреса хоста, что мы и используем для проверки (поз. 14). Если интернет-адрес сокращенный, задействуем функцию urljoin () из
Часть 266 модуля Практическое Руthоп-программирование 111. urllib.parse, чтобы превратить его в полный (поз. 15). Первым параметро._. функции передается строка, составленная из обозначения протокола и адреса хоста (мы подготовили ее заранее - на поз. а вторым 3), - сокращенный интернет­ адрес. Загружаем (поз. 16), графический файл, находящийся по полученному интернет-адресу проверяем, завершилась ли его загрузка успехом (поз. содержимое файла в виде последовательности байтов типа ряем, превышает ли его размер число 17), извлекаем bytes (поз. 18) и прове­ 1024 (поз. 19). Теперь необходимо извлечь из пути к файлу его имя. Сделать это очень просто: по­ лучаем из атрибута path объекта со сведениями об интернет-адресе файла путь к нему, разбиваем его на части по символу слеша, вызвав метод split () строки (см. разд. 4.2.3.3), методом (поз. и получаем последний элемент списка частей, возвращенного 20). Вызовом функции j oin () из модуля os. path стандартной библиотеки составляем из пути к целевой папке и только что полученного имени файла полный путь к этому файлу и сразу же открываем его для записи в двоичном режиме (поз. ем в открытом файле содержимое загруженного файла (поз. (поз. 22) 21 ). Сохраня­ и закрываем файл 23). Если какой-либо файл загрузить не удалось, выводим соответствующее сообщение в консоли (поз. 24). Объявив функцию do _ work (), сразу же запускаем ее (поз. 25). Попробуем запустить только что написанную программу, подав в консоли команду, например: 16.2.ру https://Зdnews.ru 16.3. c:\work\downloaded Библиотека aiohttp: конкурентная загрузка файлов Загрузка файлов из Интернета занимает много времени. Поэтому загрузку множе­ ства файлов лучше выполнять конкурентно - это существенно повысит произво­ дительность. Конкурентную загрузку файлов упрощает дополнительная библиотека версия 3 .12. *, pip install aiohttp. о которой рассказывается в книге, устанавливается командой: "aiohttp==З.12" На заметку Полная документация по библиотеке https://docs.aiohttp.org/. aiohttp находится по интернет-адресу: Ее
Урок 1б. Загрузка и анализ данных из Интернета 16.3.1. Aiohttp: 267 отправка клиентских запросов Отправка запросов выполняется посредством объекта клиентской сессии, создавае­ мого на основе класса Clientsession из модуля aiohttp. Вот формат вызова конст­ руктора этого класса: ClientSession([<бaзoвый интернет-адрес>=Nоnе] [, ] [headers=None]) Базовый интернет-адрес задается в виде строки и должен завершаться символо}и слеша. Сокращенные интернет-адреса, указываемые в вызовах метода, который не­ посредственно загружает файлы и описывается далее, добавляются к нему. Если базовый интернет-адрес не задан, в вызовах упомянутого метода необходимо зада­ вать полные интернет-адреса. Параметр headers задает заголовки, отправляемые с каждым запросом, в том же формате, что используется при вызове функции get () (см.разд. из библиотеки requests 16.1.1). Класс Clientsession является менеджером контекста. Работа с созданным объектом клиентской сессии выполняется в теле обработчика контекста. По завершении работы с клиентской сессией она автоматически закрывается с закрытием всех не­ закрытых сетевых соединений. Для отправки клиентского запроса посредством созданной клиентской сессии при­ меняется метод get () gеt(<интернет-адрес класса ClientSession: файпа>[, params=None] [, headers=None]) Интернет-адрес загружаемого файла задается в виде строки. Если в вызове конст­ руктора класса Clientsession был задан базовый интернет-адрес, то следует задать сокращенный интернет-адрес файпа, который будет добавлен к базовому. Параметры params и headers имеют то же назначение, что и одноименные парамет­ ры функции get () из библиотеки requests, а их значения задаются в тех же фор­ матах. Метод возвращает полученный серверный ответ в виде объекта класса ClientResponse 1 из модуля aiohttp. 16.3.2. Aiohttp: обработка серверных ответов Класс ClientResponse является менеджером контекста. Работа с ответом, представ­ ленным объектом этого класса, производится в теле обработчика контекста. Класс ClientResponse поддерживает атрибуты ok, reason и headers, аналогичные одноименным атрибутам класса Response библиотеки requests (см. разд. Также он поддерживает следующие полезные атрибуты: ♦ status - ♦ charset - 1 аналог атрибута status_code класса Response; аналог атрибута encoding класса Response. Логичнее было бы назвать его ServerResponse, поскольку ответ присылается сервером. 16.1.2).
Часть 268 Для извлечения методы класса ♦ содержимого ответа 111. Практическое Руthоп-программирование применяются следующие сопрограммы­ ClientResponse: text ( [encoding=None]) - возвращает фьючер с текстовым содержимым текуще­ го ответа, представленным в виде строки. В параметре encoding задается строко­ вое обозначение текстовой кодировки, используемой для декодирования ответа. Если задать значение None, кодировка будет определена автоматически; ♦ возвращает фьючер с двоичным содержимым текущего ответа, пред­ read ( ) - ставленным последовательностью байтов типа bytes; ♦ j son ( [encoding=None] ) в формате JSON, возвращает фьючер с содержимым текущего ответа представленным в виде словаря. В параметре encoding задается строковое обозначение текстовой кодировки, используемой для декодирования ответа. Если задать значение None, кодировка будет определена автоматически. Снова попытаемся загрузить и сохранить в файле главную страницу многостра­ дального сайта 3DNews.ru, теперь уже с помощью библиотеки aiohttp: >>> from aiohttp import ClientSession >>> from asyncio import run >>> async def run_load(): async with ClientSession() as session: async with session.get('https://Зdnews.ru/') as r: print(r.ok, r.status) f = open(r'c:/work/downloaded/Зdnews.ru.html', mode='w', encoding='utf-8') f.write(await r.text()) f. close () >>> run(run_load()) True 200 16.3.3. Aiohttp: загрузка больших файлов Сначала следует обратиться к атрибуту content класса ClientResponse. Атрибут хранит особый объект потока, представляющий содержимое полученного ответа. Объект потока поддерживает метод i ter _ chunked (<размер чанка>), который воз­ вращает асинхронный итератор, выдающий последовательность чанков с содержи­ мым потока. Размер чанка должен быть задан в виде целого числа в байтах. Ради эксперимента загрузим и сохраним главную страницу всё того же сайта 3DNews.ru по чанкам в 128 Кбайт каждый: >>> async def run_load(): async with ClientSession() as session: async with session.get('https://Зdnews.ru/') as r: f = open(r'd:/work/!temp/Зdnews.ru.html', mode='ab') async for chunk in r.content.iter chunked(l28 * 1024): f.write(chunk)
Урок 1б. Загрузка и анализ данных из Интернета f. 269 close () >>> run(run_load()) 16.3.4. Программа, сохраняющая изображения с заданной веб-страницы, версия Напишем версию 2.0 2.0 программы для сохранения изображений с указанной страни­ цы, на этот раз конкурентную (листинг 16.3 ). Листинг 16.3. Программа дпя сохранения изображений с указанной страницы, версия 2.0 (исправления) from requests import get fram aiohttp i.mport Clientsession i.mport asyncio file_list = [] # 1 async def load_file(file_list, session): while True: # 2 # # 3 4 5 try: file = file_list .pop(O) async with session.get(file[O]) as img_r: if img_r.ok: file content = await img_r.read() if len(file_content) > 1024: f = open(file[l], mode='wЬ') f.write(file_content) f .close() else: print(f'Фaйn {file[O]} не удаnось except IndexError: break загрузить') async def do_ work О : async wi th Clientsession () as session: async with session . get(p . url) as page_r: if paqe_r.ok: page_html = await page_r . text() parser = ВeautifulSoup(page_html, 'html .parser') img_tags = parser. Ьоdу ( • img • ) for img_tag in img_tags: img_url = img_tag['src'] img_url_obj = urlparse(img_url) #
Часть 270 111. Практическое Руthоп-программирование if img url_ obj. scheme ! = 'data' : if not img_url_obj.scheme or \ not img_url_obj.netloc: img_url = urljoin(page_Ьase_url, img_url) file_name = img_url_obj.path.split(sep='/') [-1] file_list.append( (img_url, # 6 join(p.destination,file_name))) (asyncio.create_task(load_file(file_list, tasks # 7 session )) \ for r in range(4)) await asyncio.gather(*tasks) # 8 asyncio.run(do_work()) # 9 Сначала загрузим страницу, найдем все присутствующие на ней графические изо­ бражения, занесем их в список, а уже потом будем их обрабатывать. Позже мы запустим четыре экземпляра сопрограммы, которая станет выбирать файлы из списка, загружать и сохранять их. Создадим пустой список, в который потом будем «складывать» файлы, подлежа­ щие загрузке (поз. 1 в листинге 16.3). Сведения о каждом файле, добавляемые в этот список, будут представлять собой кортеж из двух элементов: полного интер­ нет-адреса файла и полного пути, по которому он должен быть сохранен. Объявим сопрограмму load_ file ( J, которая будет загружать и сохранять файлы из списка и которую мы потом запустим в четырех экземплярах (поз. 2). В качестве параметров она получит ссылку на список с файлами и объект клиентской сессии aiohttp. Мы используем один объект клиентской сессии для загрузки и страницы, и графических файлов - это сэкономит память и повысит производительность. Код сопрограммы практически полностью взят из листинга 16.2 и не требует пояснений. Далее объявим сопрограмму do work ( J, в которой и будет выполняться вся работа (поз. 3). В ее теле создаем объект клиентской сессии (поз. жаем страницу (поз. 5) 4), с его помощью загру­ и обрабатываем ее так же, как и в предыдущей версии про­ граммы, с поправкой на особенности библиотеки aiohttp. Сведения о каждом най­ денном на ней графическом файле (кортеж с интернет-адресом и целевым путем для сохранения) добавляем в созданный ранее список (поз. 6). Под конец уже зна­ комым способом создаем, запускаем четыре задачи с экземплярами сопрограммы load_ file ( J (поз. 7) и дожидаемся, когда они выполнятся (поз. Останется лишь запустить сопрограмму do _work () (поз. На компьютере автора версия 2.0 8). 9). программы выполнилась существенно быстрее, нежели предыдущая. Все дело в том, что в Python реализованы высокоэффективные механизмы для конкурентной загрузки данных по сети, которые используются биб­ лиотекой aiohttp и дают значительное повышение производительности по сравне­ нию с обычными, неконкурентными механизмами, применяемыми библиотекой requests.
Урок 1б. 16.4. 1. 271 Загрузка и анализ данных из Интернета Самостоятельные упражнения Напишите программу, которая выводит заголовки, краткое содержание и интер­ нет-адреса статей из нижнего блока главной страницы сайта ЗDNews.ru (с заго­ ловком Новое в обзорах). Элементы страницы, в которых выводятся эти сведе­ ния, отыщите самостоятельно, пользуясь инструментами разработчика, встроен­ ными в веб-обозреватель. 2. Измерьте время выполнения версии 1.0 программы для загрузки и сохранения изображений. Используйте для этого всё тот же декоратор profiler (). У автора изображения с главной страницы сайта ЗDNews.ru были загружены и сохранены за 3. 11,882 с. Измерьте время выполнения версии 2.0 программы для загрузки и сохранения изображений с той страницы, что использовалась для тестирования предыдущей версии программы. Поскольку декоратор для этой цели не подойдет, встройте код, измеряющий производительность, непосредственно в код программы (пример У автора изображения были загружены и сохранены за 2,4988 с. - в разд. 14. 6).
Урок17 Разработка веб-приложений, часть Ранее разработка веб-приложений на основе которых языков НТМL, самим генерировались и CSS JavaScript. Python страницы, 1 требовала написания шаблонов, на и, соответственно, хорошего знания Но сейчас для этого достаточно владеть лишь Python. Дополнительная библиотека Python NiceGUI позволяет легко и быстро разрабатывать на веб-приложения любой сложности, даже не зная У становить версию 2.23. * HTML, CSS и JavaScript 1. этой библиотеки, описываемую в книге, можно коман­ дой: pip install "nicegui==2.23" В процессе установки также будет загружено множество дополнительных библио­ тек, необходимых NiceGUI для работы. На заметку Полное описание библиотеки NiceGUI можно найти по интернет-адресу: https://nicegui.io/. Внимание! Все функции и классы, рассматриваемые на этом уроке, объявлены в модуле nicegui. ui, если явно не сказано иное. 17 .1. Основы библиотеки 17.1.1. Страницы и маршруты Страница 11 NiceGUI ной функции веб-страница, выводимая веб-приложением. Описывается в виде обыч­ Python, которая может иметь произвольное имя. В теле этой функции формируется интерфейс страницы - все элементы, которые должны присутствовать на ней: абзацы, заголовки, списки, таблицы, изображения, веб-формы с элементами управления и др. Интерфейс полностью описывается средствами 1 Хотя Python. знание этих языков не помешает.
Урок 17. Разработка веб-приложений, часть 273 1 Каждая страница связывается с определенным интернет-адресом, точнее, интернет­ путем. При переходе по этому пути загружается и выводится связанная с ним стра­ ница. Маршрут 11 описание интернет-пути, с которым связывается определенная страница. Шаблонный путь - интернет-путь, записываемый в маршруте. Маршруr описывается декоратором title=None]) раgе(<шаблонный путь>[, Шаблонный page ( ) : путь здесь записывается в виде строки и должен начинаться с символа слеша. Параметр title задает название страницы, выводимое на вкладке веб­ обозревателя. Если он не указан, библиотека даст странице стандартное название «NiceGUI». Давайте напишем простое приложение, содержащее три страницы (листинг 17 .1 ). Первая страница станет главной и будет связана с шаблонным путем/ (слеш), вто­ рая покажет сведения о приложении и будет связана с путем /about, а третья стра­ ница, рассказывающая о правах разработчиков, - с путем /rights. from nicegui import ui # 2 1 3 ui. run () # 4 Как говорилось ранее, отдельная страница описывается функцией (поз. 1в листин­ @ui.page('/', def main (): # titlе='Главная') # .classes('text-hl') /about, ' ui.label('Пpимep веб-приложения') ui.lаЬеl('Перейдите по пути 'чтобы попасть на страницу сведений о приложении') ui.lаЬеl('Перейдите по пути 'чтобы просмотреть @ui.page('/about', title='O def about(): /rights, ' права разработчиков') приложении') ui.label('Пpимep веб-приложения') ui.label('Haпиcaнo @ui.page('/rights', def rights(): с ui.label('Bce права принадлежат '"Python. 22 урока для 17 .1) работу библиотеки NiceGUI') title='Пpaвa разработчиков') ui.label('Пpимep веб-приложения') ге .classes('text-hl') целью проверить .classes('text-hl') читателям книги начинающих"') с декоратором page (), в котором задается шаблонный путь, связываемый со страницей (поз. 2).
Часть 274 Функция label () создает надпись 111. Практическое Руthоп-программирование обычный текст, выводимый на странице (поз. - 3). В параметре задается строка с текстом надписи. Функция возвращает объект, пред­ ставляющий созданную надпись. Объект, представляющий элемент страницы (в том числе и надпись), поддерживает метод (поз. classes () 3). в котором задаются стилевые классы для текущего элемента В частности, стилевой класс text-hl выводит надпись крупным шрифтом и делает ее тем самым похожей на заголовок первого уровня. Для запуска приложения достаточно вызвать функцию run () (поз. 17.1.2. 4). Запуск и остановка веб-приложения Для запуска веб-приложения достаточно запустить в консоли Руthоn-модуль, в ко­ тором оно хранится. Через небольшое время откроется веб-обозреватель, заданный в системных настройках как применяемый по умолчанию, и в нем отобразится главная страница приложения (рис. 17 .1 ). О Главная + х Q CJ о V http:J/127.0.0. 1:8080 ХА * .::!:, t) х )) Пример веб­ приложения Перейдите по пути /about. чтобы попасть на страницу сведений о приложении Перейдите по пути /rights, чтобы просмотреть права разработчиков Рис. 17.1. Главная страница веб-приложения из листинга 17.1 Главную страницу можно открыть и самостоятельно, запустив веб-обозреватель и перейдя по интернет-адресу Перейдем по bttp://127.0.0.1:8080 интернет-адресу или http:/Лocalhost:8080. Ьttp://127.0.0.1:8080/about, т. е. по В ответ будет выведена страница со сведениями о приложении (рис. пути /about. 17.2). Вернемся на главную страницу, нажав кнопку Назад веб-обозревателя, и завершим работу приложения, переключившись на экземпляр консоли, в котором она была запущена, и нажав комбинацию клавиш <Ctrl>+<C> или просто закрыв консоль. Когда веб-приложение запущено, оно отслеживает любые изменения, сделанные в его коде, и автоматически перезапускается, чтобы эти изменения вступили в силу.
Урок 17. Разработка веб-приложений, часть е о приложении + х Q () 275 1 о V ХА hltp://127.0.0.1 :80 * .±, х tJ » Пример веб­ приложения Написано с целью проверить работу библиотеки Рис. 17.1.3. 17.2. Страница со сведениями о веб-приложении из листинга 17.1 Создание гиперссылок Гиперссылка NiceGUI создается функцией link (). Первым параметром в ее вызове задается текст гиперссылки, вторым - интернет-адрес целевой страницы. Дополним наше приложеньице из листинга другие страницы (листинг Листинг NiceGUI 17.2. @ui.page('/', def main (): 17.1 гиперссылками для перехода на 17.2). Простейшее веб-приложение с навигацией (исправления) titlе='Главная' ) ui. label ( 'ПеfЭей:а,1,;ге ui. label ( 'Пеrэей:а,1,;ге ui . link ( 'Све,цения о ui . link ( 'Све,цения о по пу,;гн ,'about, по п:, 1 н1 /Fi~нts 1 правах главнуJО', @ui.page('/rights', def rights(): ui. link ( I На . . . ') , ' / aЬout' ) разрабо'l'ЧИJСОВ' , ' / rights ' ) приложении' @ui.page('/about', title='O def about( ) : ui.link ( 'На . . . ') 1 / приложении') 1 ) title='Пpaвa разработчиков' ) ГЛаануJС) I f I / 1 )
Часть 276 111. Практическое Руthоп-программирование Запустим исправленное приложение, если уже остановили его, или подождем, пока оно само не перезапустится. Попробуем перейти по какой-либо гиперссылке и посмотрим, что получится. 17 .1.4. Параметризованные маршруты и URL-параметры Часто бывает необходимо передать от одной страницы другой странице какие-либо значения. Сделать это можно, вставив их непосредственно в путь, ведущий на целевую страницу. URL-napaмemp - произвольное значение, передаваемое другой странице в составе пути, ведущего на нее. Параметризованный маршрут - маршрут, содержащий URL-параметры. URL-параметр в составе шаблонного пути записывается в следующем формате: { { <имя ИRL-параметра>)} Имя может быть выбрано произвольно. Однако оно должно быть уникальным в пре­ делах шаблонного пути и удовлетворять требованиям, предъявляемым к именам переменных Python (см.разд. 1.4). Чтобы получить значения URL-параметров, следует в объявлении функции, реали­ зующей страницу, указать параметры с именами, совпадающими со именами URL- параметров. У параметра функции страницы, соответствующего URL-параметру, можно указать аннотацию типа ( см. разд. 15.1) - тогда значение URL-параметра будет автомати­ чески преобразовано в тип, заданный в аннотации. Дополним простейшее веб-приложение из листинга 17 .2 еще одной страницей, вы­ водящей значение URL-параметра nшn, переданного ей в составе пути. А на главной странице поместим пару гиперссылок этого URL-параметра (листинг с путями, содержащими 17.3). def rnain () : ui.link('URL-пapaмeorp "num" '/param/123456789' ) ui.link('URL-пapaмeтp "num" имеет значение "123456789"', имеет значение "aЬcdefgh"', '/param/aЬodefgh') @ui. page ( '/param/ {num} ' , t i tle=' URL-параметр • ) clef param(num: str): ui. laЬel (f 'Значение URL-параметра num: {num} ') разные значения
Урок 17. Разработка веб-приложений, часть 277 1 В объявлении функции pararn (), реализующей новую страницу, мы записали пара­ метр num, соответствующий одноименному URL-параметру, и указали у него стро­ ковый тип данных. Запустим исправленное приложение, если уже остановили его, или подождем, пока оно само не перезапустится. Перейдем по одной из вновь добавленных гиперссы­ лок и посмотрим, появилось ли на странице значение, переданное с URL-пара­ метром. После чего вернемся на главную страницу и перейдем по второй гипер­ ссылке. Следует запомнить важный момент. Все имеющиеся маршруты просматриваются библиотекой в том порядке, в котором они созданы в коде приложения, и как толь­ ко будет найден маршрут с совпадающим шаблонным путем, просмотр маршрутов прекращается. Эта особенность может стать причиной неприятных коллиз·ий. Рассмотрим сле­ дующий код: ui .page ( '/ { filenarne) '): def details(filenarne: str): ui. page ( ' / about' ) def about () : При переходе по интернет-пути /about библиотека первым встретит маршрут с шаблонным путем /{filenarne), посчитает его совпавшим, вызовет функцию details () и передаст ей с параметром значение about. Файла с именем about, скорее всего, не обнаружится, и мы получим ошибку. Но если поменять маршруты местами: ui.page('/about') def about(): ui.page('/{filenarne)'): def details(filenarne: str): будет вызвана «правильная» функция about () . 17.1.5. Обработка событий Событие -уведомление о том, что в программе что-либо произошло (пользователь нажал кнопку, навел курсор мыши на определенный элемент и т. п.). Обработчик события - функция, вызываемая при возникновении заданного собы­ тия в указанном элементе страницы с целью отреагировать на это событие опреде­ ленным образом. Для задания обработчика события, возникающего в каком-либо элементе страницы, следует вызвать у этого элемента метод on () . Первым параметром методу переда-
Часть 278 Практическое Руthоп-программирование 111. ется имя обрабатываемого события в виде строки. Так, например, событие щелчка мышью имеет имя click. Вторым параметром передается ссылка на обработчик. Добавим в наше приложение из листинга надписью, тинг выводящей количество 17 .3 щелчков, еще одну страницу сделанных на - этой с кнопкой и кнопке (лис­ 17.4). Листинг 17.4. Простейшее веб-приложение с подсчетом количества щелчков на кнопке (исправления) @ui . page ( ' / coun ter ' , ti tle= 'Подсчет def counter(): cnt = щалчхов ') # 1 4 5 = cnt # # # # 7 = ui.laЬel(cnt) # 2 ) . on ( 'click' , add) . classes ( 'cursor-pointer' ) # 3 О def add(): nonlocal cnt cnt += 1 laЬel count.text laЬel_count ui. laЬel ( '\ЦеЛJСНИ меня' 6 В функции-странице сначала создаем переменную cnt для хранения количества щелчков с изначальным значением О (поз. 1 в листинге 17.4). Далее создаем надпись, выводящую количество щелчков из подготовленной ранее переменной, и сохраняем ссылку на представляющий ее объект в другой перемен­ ной, (поз. 2). нам потребуется обращаться к этой надписи из обработчика события Создаем другую надпись, на которой пользователь будет щелкать мышью. У нее вызовом метода on ( 1 указываем в качестве обработчика события click функцию add (), которую рассмотрим чуть позже (поз. 3). Также вызовом метода classes () задаем у надписи стилевой класс cursor-pointer, который укажет при наведении курсора мыши изменить его форму на «указующий перст». Объявляем функцию add 11, которая станет обработчиком события click, возни­ кающего во второй надписи (поз. 4). В теле этой функции помечаем переменную cnt, хранящую количество щелчков, как нелокальную (см. разд. личиваем ее значение на 1 (поз. 6) 6.5 - поз. 5), уве­ и выводим обновленное количество щелчков во второй надписи, присвоив ее свойству text значение переменной cnt (поз. 7). Когда приложение запустится или перезапустится, перейдем по интернет-адресу http://127.0.0.1/counter, щелкнем несколько раз на кнопке и проверим, увеличива­ ется ли выведенное на странице количество щелчков (рис. 17.3 ).
Урок 17. Разработка веб-приложений, часть 1 279 8 Щелкни меня Рис. 17.1.6. Функция 17.3. run(), Страница со счетчиком щелчков запускающая веб-приложение Для запуска веб-приложения служит функция run () : run([host='0.0.0.0'] [, ] [port=BOBO] [, ] [title='NiceGUI'] [, [favicon=None] [, ] [dark=False] [, ] [language='en-US'] [, [show=True] [, ] [reload=True] [, ] [storage_secret=None]) Параметров здесь довольно много: интернет-адрес, ♦ host - по которому будет доступно запущенное веб-при­ ложение, в виде строки. Если задать значение 'о.о.о.о', веб-приложение будет доступно по всем интернет-адресам, назначенным текущему компьютеру; ♦ port - ♦ ti tle - номер ТСР-порта, через который будет работать приложение; название для страниц, у которых оно не было задано в одноименном параметре декоратора page () (см.разд. ♦ favicon - 17.1.1); интернет-адрес со значком приложения в виде строки. Если задать значение None, будет выводиться значок, поставляемый в составе ♦ dark - NiceGUI; если True, будет включена темная тема приложения, если False - свет­ лая, если None, тема приложения будет соответствовать системной; ♦ language - обозначение языка веб-приложения, которое будет записано в атри­ бут lang тега <html> всех страниц, генерируемых библиотекой. Русскому языку соответствует обозначение 'ru-RU', американскому английскому ♦ show - - 'en-us'; если тrue, сразу после запуска веб-приложения его главная страница будет открыта в веб-обозревателе, установленном по умолчанию, если False не будет; ♦ reload - если тrue, веб-приложение будет отслеживать изменения, сделанные в его коде, и автоматически перезапускаться, если False - ♦ storage _ secret - не будет; секретный ключ, который используется для шифрования дан­ ных, записываемых в хранилища NiceGUI (см. разд. 19.2). Должен представлять собой как можно более длинную строку, содержащую по возможности случай­ ный набор символов. Пример: ui.run(port=BO, titlе='Фотогалерея', favicon='/others/icons/fav.png', dark=None, language='ru-RU', show=False, reload=False)
Часть 280 17 .2. 111. Практическое Руthоп-программирование Основные элементы страниц Каждый из элементов страниц, предоставляемых NiceGUI, создается вызовом осо­ бой функции, которая возвращает объект, представляющий этот элемент. Если с элементом предполагается какое-либо взаимодействие (например, занесение в надпись нового текста), ссылку на этот объект следует сохранить в отдельной пе­ ременной. 17.2.1. Надпись Надпись, выводящая заданный в виде строки текст, создается вызовом функции label (<текст надm1си>). Надпись представляется классом Label. Он поддерживает два полезных свойства: ♦ text - ♦ visiЫe - 17.2.2. текст, выводимый в надписи; если True, надпись будет видима, если False - нет. Графический элемент Графический элемент, выводящий изображение из файла с заданным интернет­ адресом, создается функцией image ( ) : imаgе(<интернет-адрес файла с изображением>) Пример: ui.image('/images/products/bike.jpg') Графический элемент представляется классом Image. Он поддерживает свойства и source, хранящее интернет­ адрес графического файла, а также метод force _ reload (), который перезагружает visiЫe, знакомое нам по надписи (см. разд. 17.2.1), файл с изображением. Класс графического элемента поддерживает функциональность менеджера контек­ ста. В теле обработчика контекста можно создать элементы, которые будут выво­ диться поверх изображения, например: with ui.image('/images/products/auto.jpg'): ui. label ( 'Скорость и удобство! ' ) .classes('absolute-bottom text-subtitle2 text-center') 17.2.3. Гиперссылка Гиперссьшка link (<текст NiceGUI создается гиперссылки>, вызовом функции link (): <указание на целевую страницу> [, new_ tab=False] ) Текст гиперссылки задается быть определено в виде: в виде строки. Указание на целевую страницу может
Урок Разработка веб-приложений, часть 17. 1 281 ♦ сокращенного интернет-адреса в виде строки: ui.link('O ♦ приложении', '/about') полного интернет-адреса в виде строки: ui.link('Ha ♦ 'https://bhv.ru/') сайт БХВ', ссылки на функцию, реализующую целевую страницу: ui.link('Пpaвa разработчиков', Если параметру rights) new_ tab дать значение False, целевая страница откроется в текущей • вкладке веб-обозревателя, если тrue Гиперссьтка представляется visiЫe (см.разд. в новой вкладке. классом Link. Он поддерживает свойства text и 17.2.1). Помимо того, класс Link имеет функциональность менеджера контекста. В обра­ ботчике контекста в содержимое гиперссылки можно добавить произвольные эле­ менты - например, надписи и графические изображения: with ui.link('', '/products'): нi. label ('Список продуктов') . classes ( 'text-hб' ) ui.image('/images/products/product-icon.png') 17.2.4. 11 Текст в формате Markdown простой и компактный язык разметки форматированного текста. Markdown 1 - Для вывода заданного текста, отформатированного с помощью Markdown, служит элемент, создаваемый функцией markdown (): markdown(<тeкcт в Markdown>[, формате extras=['fenced-code-Ьlocks', 'taЫes']]]) Параметр extras задает список с названиями расширений2, которые будут задейст­ вованы при выводе текста Пример (рис. Markdown. 17.4): .markdown (''' ### Пример нi Это текст Пример в кода формате на языке **Markdown**. Python: '' 'python from nicegui import ui нi. label ( 'Надпись' ) '' ') 1 bttps://www.markdownguide.org/ 2 Перечень поддерживаемых расширений находится по интернет-адресу: bttps://gitbub.com/trentm/pytbon-markdown2/wiki/Extras#implemented-extras.
Часть 282 111. Практическое Руthоп-программирование Пример с таблицей Пример Это текст в формате Название Markdown. Описание Пример кода на языке Python: Python Язык программирования from nicegui import ui NiceGUI Библиотека ui.lаЬеl('Надпись') Рис. 17.4. Рис. Пример текста в формате 17.5. Пример текста в формате Markdown, содержащего таблицу Markdown Пример Markdown-тeкcтa с таблицей (рис. 17.5): ui .markdown (''' ### Пример с таблицей 1 Название I Описа ние 1----------------------------------1 Язык программирования 1 1 Pythoп Библиотека 1 NiceGUI ' ' ', extras= [ 'taЬles' ] ) I I Элемент Markdown представляется классом мarkdowп, который поддерживает свой­ ства visiЫe (см. разд. 17.2.1) и content, которое хранит Markdown-тeкcт, выводя­ щийся в текущем элементе. 17.2.5. Аудиоролик Аудиоролик, воспроизводящий аудиофайл с заданным интернет-адресом, создается функцией audio () : аudiо(<интернет-адрес аудиофайла>[, controls=True] [, autoplay=False] [, muted=False] [, loop=False]) Остальные параметры: ♦ contro ls - если тrue, на экране будут выведены элементы управления воспро­ изведением, если False ♦ не будут; autoplay - если тrue, ролик начнет воспроизводиться сразу после загрузки, если False - не начнет; не будет; ♦ muted - если True, звук изначально будет приглушен, если False - ♦ если тrue, воспроизведение ролика будет зациклено, если False - l oop - не будет. Пример: ui.audio('/media/audio/greeting.mpЗ', Аудиоролик представляется классом (см.разд. 17.2.1), а также: autopla y=True) Audio. Он поддерживает свойство vis iЫe
Урок 17. Разработка веб-приложений, часть ♦ source - ♦ pause () - ♦ play () - ♦ 283 1 свойство, интернет-адрес воспроизводящегося аудиофайла; метод, приостанавливает воспроизведение текущего ролика; запускает или возобновляет воспроизведение текущего ролика; seek (<позиция>) - устанавливает текущий ролик на заданную позицию воспроиз­ ведения, указываемую в виде вещественного числа в секундах. 17.2.6. Видеоролик Видеоролик создается функцией video (). Формат ее вызова такой же, как и у функ­ ции audio () (см. разд. 17.2.5). Пример: ui.video('/media/video/test-drive.mp4', muted=True) Видеоролик представляется классом Video, поддерживающим те же свойства и методы, что и класс Audio (см. разд. 17.2.7. 17.2.5). Фрагмент программного кода Для вывода заданного фрагмента программного кода достаточно вызвать функцию code (): соdе(<фрагмент программного кода>[, language='python']) В параметре language указывается название языка программирования, на котором набран фрагмент. Пример (рис. 17.6): ui . code ( ' ' ' from nicegui import ui ui. label ('Это надпись') ui. run () '' ') Фрагмент программного кода представляется классом Code. Он поддерживает те же свойства и методы, что и класс мarkdown (см. разд. 17.2.4). from nicegui import ui ui . label('Этo надпись') ui . run() Рис. 17.6. Фрагмент программного кода
Часть 284 17 .3. 111. Практическое Руthоп-программирование Разметочные элементы Описываемые далее элементы предназначены для создания разметки у страницы. 17.3.1. Блок-строка Блок-строка выстраивает элементы-потомки по горизонтали. Она создается вызо­ вом функции row ( ) : row( [wrap=True] [, ] [align_items=None]) Параметры: ♦ wrap - если тrue, при недостатке свободного места по горизонтали потомки будут переноситься по строкам, если False - ♦ align_items - не будут; обозначение режима выравнивания потомков по вертикали: None или 'start' (по верху), 'center' (по центру), 'end' (по низу) или 'stretch' (рас­ тягивание по вертикали). Блок-строка представляется классом Row. Он обладает функциональностью менед­ жера контекста. Добавление потомков в блок-строку производится в теле обработ­ чика контекста. Пример (рис. 17. 7): with ui.row() .classes('full-width bg-grey-4'): ui. label ( 'Python' ) ui. label ( 'JavaScript') ui. label ( 'Java' ) ui. label ( 'RuЬy') 1 Pytt,on JavaS<:Opt Ja,,a Рис. 17.7. RuЬy Блок-строка с четырьмя потомками Стилевой класс full -width задает ширину равной ширине родителя, а стилевой класс bg-grey-4 - светло-серый цвет фона. Блок-строка поддерживает свойство visiЫe (см.разд. 17 .3.2. 17.2.1). Блок-колонка Блок-колонка выстраивает элементы-потомки по вертикали. Она создается функци­ ей col (), имеющей тот же формат вызова, что и функция row() (см. разд. 17.3.1). Параметр align_iterns этой функции задает выравнивание потомков по горизонта­ ли: None или 'start' (по левому краю), 'center' (по центру), 'end' (по правому краю) или 'stretch' (растягивание по горизонтали). Блок-колонка представляется классом Col, аналогичным классу Row (см.разд. 17. 3.1).
Урок 17. Разработка веб-приложений, часть 1 17.3.3. 285 Разделитель Разделитель отделяет друг от друга элементы-потомки в блоке-строке или блоке­ колонке. Он создается функцией separator (). Пример (рис. 17 .8): with ui.row() .classes('full-width bg-grey-4'): ui.label('Python') ui. label ( 'JavaScript') ui. label ( 'Java') ui. separator () ui. label ( 'Ruby') 1 Python Рис. 17.8. JavaScnpt J8!/a Блок-сrрока с разделителем между третьим и четвертым потомками Разделитель представляется классом Separator, поддерживающим свойство visiЫe (см. разд. 17.2.1). 17 .3.4. Блок-сетка Блок-сетка выстраивает элементы по столбцам и строкам таблицы. Этот элемент создается функцией grid () : grid([rows='auto lfr'] [, ] [colшnns='auto lfr']) В параметрах rows и colшnns задаются сведения о соответственно строках и столб­ цах создаваемой сетки. Значение любого из этих параметров можно указать в виде: ♦ целого числа - задаст количество строк или столбцов. Все строки и столбцы будут иметь одинаковые высоту и ширину: with ui.grid(rows=2, colшnns=З): ♦ строки из обозначений размеров строк или столбцов, разделенных пробелами. Каждое обозначение может быть задано в виде: • auto - размер будет таким, чтобы только вместить содержимое строки или столбца; • значения размера в формате CSS с использованием любой поддерживаемой единицы измерения; • величины формата <количество свободных долей>fr. Пример: with ui.grid(colшnns='auto lOOpx 20pt lfr 2fr'):
Часть 286 111. Практическое Руthоп-программирование Первый столбец получит ширину, равную ширине его содержимого, второй­ ширину в 100 пикселов, третий в - 20 типографских пунктов. Оставшееся про­ странство в таблице будет разделено на три (нr + 2fr) равные доли; четвертый столбец будет иметь ширину, равную одной такой доле, пятый - равную двум долям. Блок-сетка представляется классом Grid. Он является менеджером контекста, и за­ дание его потомков производится в теле обработчика контекста. Пример (рис. with 17.9): ui.grid(colшnns='lfr Зfr auto 20рх', rows='20px auto auto'): ui. label ('Название') ; ui. label ( 'Описание') ui.label ('Сайт'); ui. label ( 'Веб?') ui. label ( 'Python') ; ui. label ('Язык программирования') ui.label('https://www.python.org/'); ui.label(' ') ui. label ( 'NiceGUI'); ui. label ('Библиотека') ui.label('https://nicegui.io/'); ui.label('*') Описание Сайт Python Язык программирования https://www.python.org/ NiceGUI Библиотека https://nicegui.io/ Рис. 17 .3.5. 17.9. * Блок-сетка Спойлер Спойлер 11 Веб? Название панель, разворачивающаяся и сворачивающаяся при щелчках на ее заго­ ловке. Спойлер создается вызовом функции expansion () : expansion (<текст заголовка> [, caption=None] [, group=None] [, value=False] [, on_value_change=None]) текст заголовка задается в виде строки. Остальные параметры: ♦ ♦ caption дополнительный текст, выводящийся под заголовком. Если задать None, никакой дополнительный текст выводиться не будет; group - имя группы, в которую входит создаваемый спойлер. Из всех спойле­ ров, находящихся в одной группе, может быть развернут лишь один. При разво­ рачивании другого спойлера ранее развернутый сворачивается; ♦ value - ♦ если тrue, спойлер изначально будет развернут, если False - on_value_change - свернут; ссылка на функцию, вызываемую при разворачивании или сворачивании спойлера. Спойлер представляется классом Expansion. Он обладает функциональностью менеджера контекста. Добавление элементов в тело спойлера производится в теле обработчика контекста.
Урок 17. Разработка веб-приложений, часть Пример (рис. 1 287 17.10): def spoiler_value changed(): mark.text = 'Спойлер развернут' if sp.value else 'Спойлер свернут' with ui.expansion('Python', caption='Teмa: разработка', on_value_change=spoiler_value_changed) as sp: ui. label ('Язык программирования' ) ui. label ( 'Исключительно мощный' ) mark = ui.label('Cпoйлep свернут') Python Python V Тема разработl(а л Тема разработка Язык программирования Сnойлер свернут Исключительно мощный Сnойлер развернут Рис. 17.1 О. Спойлер: слева - свернутый, справа - развернутый Объединив спойлеры в группу и указав имя этой группы ( оно может быть произ­ вольным) в параметре group в вызовах их конструкторов, можно создать аккордеон. 11 Аккордеон - Пример (рис. группа спойлеров, из которых может быть развернут только один. 17 .11 ): with ui.expansion('Python', group='languages'): ui. label ( 'Язык программирования' ) ui. label ( 'Исключительно мощный') with ui.expansion('JavaScript', group='languages'): ui. label ( 'Язык программирования' ) ui.lаЬеl('Применяется в клиентской веб-разработке') with ui.expansion('NiceGUI', group='languages'): ui. label ('Библиотека') ui.lаЬеl('Применяется для разработки веб-приложений') Python V л JavaScrlpt Язык программирования Применяется в клиентской веб-раэработке NlceGUI Рис. V 17.11. Аккордеон (второй спойлер развернут)
Часть 288 111. Практическое Руthоп-программирование Класс Expansion поддерживает свойство visiЫe (см.разд. ♦ ♦ если value enaЫect True, спойлер развернут, если False - если - 17.2.1), а также свойства: свернут; True, спойлер доступен для взаимодействия (развертывания и False - недоступен; свертывания), если ♦ текст заголовка. text - Этот класс также предоставляет ряд полезных методов: разворачивает текущий спойлер; ♦ open () - ♦ close () - ♦ ctisaЫe () - ♦ еnаЫе () - сворачивает текущий спойлер; делает текущий спойлер недоступным для взаимодействия; делает текущий спойлер доступным для взаимодействия. 17.3.6. Быстрая разметка страниц NiceGUI предоставляет средства для быстрой разметки страниц в традиционном стиле: с шапкой, левой и, возможно, правой колонками, основным содержимым, находящимся в центре, и поддоном. Пример (рис. 17 .12): [ui.label(f'Этo основное содержимое {r + 1)') for r in range(20)] with ui.header(): ui. label ( 'Это шапка' ) with ui.left_drawer(bordered=True, value=True): ui.label('Этo левая колонка') with ui.right_drawer(bordered=True): ui.label('Этo правая with ui.footer(): ui . label ('Это колонка') поддон') Это wanкa Это левая колонка Это основное содержимое 1 Это основное содержимое 2 Это основное сод-имое 3 Это основное содержимое 4 Это основное сqдержммое 5 Это оа,овное сод-•мое 6 Это основное содержимое 7 Это осноеное сод-имое 8 Это основное содержимое 9 Это основное сод-•мое 10 Это основное сqд-имое 11 Это nревая КОJ1ОН1(8 это nодд,он Рис. 17.12. Разметка страницы, выполненная средствами NiceGUI
Урок 17. Разработка веб-приложений, часть 289 1 Для создания разметки применяются элементы, создаваемые следующими функ­ циями: ♦ создает шапку: header () - header([value=True] [, ] [fixed=True] [, ] [bordered=False] [, [elevated=False] [, ] [wrap=True]) Параметры: • value - если True, элемент будет видим, если False - скрыт; • fixed - если тrue, элемент будет фиксирован, если False - станет прокру- чиваться вместе с основным содержимым; если • elevated- если True, элемент будет иметь тень, если False - False - не будет; если True, потомки элемента в случае недостатка места будут перено­ wrap - ситься по строкам, если False - ♦ не получит; bordered- • True, элемент получит рамку, если • left _ ctrawer () - не будут; создает левую колонку: left_drawer( [value=True] [, ] [fixed=True] [, ] [bordered=False] [, [elevated=False] [, ] [top_corner=False] [, ] [bottom_corner=False]) Параметры: • top _ corner - если True, элемент растянется до верхнего края окна веб­ обозревателя, если False - • нет; если True, элемент растянется до нижнего края окна веб­ bot tom_ corner - обозревателя, если False - нет. Остальные параметры аналогичны таковым у функции heacter () ; ♦ right_drawer() - создает правую колонку. Формат вызова такой же, как и у функции left _ drawer (); ♦ создает поддон. Формат вызова такой же, как и у функции header (). footer () - Шапка, левая, правая колонка и поддон представляются классами heacter, left _ drawer, right _ ctrawer и footer соответственно. Все они обладают функцио­ нальностью менеджеров контекста. Добавление в них элементов выполняется в телах обработчиков контекста. Элементы, созданные вне шапки, левой, правой колонок и поддона, станут основ­ ным содержимым. Классы header, left _ drawer, right _ drawer и footer поддерживают свойства visiЫe (см.разд. 17.2.1) и value (см.разд. 17.3.5), а также методы: ♦ hicte () - скрывает текущий элемент; ♦ show () - вновь выводит скрытый элемент; ♦ toggle () - скрывает текущий элемент, если он в настоящий момент выведен, и выводит, если он скрыт.
Часть 290 17.4. Если вы знаете 17.4.1. 111. HTML Практическое Руthоп-программирование и CSS ... Создание произвольных НТМL-элементов В модуле nicegui.html объявлены функции, с помощью которых можно создавать любые НТМL-элементы. Эти функции имеют следующий формат вызова: <имя НТМL-тега> ( [ <содержимое>=Nоnе]) Если содержимое не указано, будет создан пустой элемент. Пример (рис. 17.13): from nicegui import html html. hl ( 'Фотогалерея' ) . classes ( 'text-hl' ) h tml. р ( 'Пример веб-приложения. ' ) html.р('Позволяет просматривать, добавлять html.p('Kaждaя фотография имеет название и html. address ( '\u00a9 читатели книги. и удалять фотографии.') необязательное описание') ') Фото галерея Пример ве6-приложения. Позволяет просматривать, добавлять и удалять фотографии. Каждая фотография имеет название и необязательное описание @ читатели книги. Рис. 17.13. Произвольные НТМL-элементы Класс, представляющий созданный таким образом элемент, поддерживает функ­ циональность менеджера контекста. В теле обработчика контекста в него можно добавить произвольное содержимое. Пример (рис. with 17.14): html.Ыockquote() .classes('q-ml-md'): html. hl ( 'Python') . classes ( 'text-hl') with html.p() .classes('q-mt-sm'): h tml . s t rong ( 'Исключит ель но ' ) h tml . em ( 'мощный ' ) html. span ( 'язык программирования') Стилевой класс q-ml -md создает внешний просвет слева среднего размера, а стиле­ вой класс q-mt-sm - небольшой внешний просвет сверху. Кроме того, в модуле nicegui. ui объявлена функция element (<имя НТМL-тега>), соз­ дающая элемент на основе тега с заданным именем. Заполнение его содержимым производится в теле обработчика контекста. Пример (рис. 17.15):
Урок 17. Разработка веб-приложений, часть 291 1 Python Искnючктельно мощный язык программирования Рис . 17.14. НТМL-элемент с произвольным содержимым Python Язык программирования Рис. 17.15. НТМL-элемент , созданный функцией element () from nicegui import ui with ui.element ( 'div') .classes('text-white bg-dark'): with ui.element('hl') .classes ( 'text-hl' ): ui. label ( 'Python ' ) with ui.element('p') .classes('q-mt-sm' ) : ui.lаЬеl('Язык программирования') Стилевой класс text-white задает белый цвет текста, а стилевой класс bg-dark - темный цвет фона . И наконец, в модуле nicegui. ui объявлена функция html (), которая помещает на страницу содержимое, описанное заданным нтмL-кодом. html (<НТМL-код помещаемого содержимого> [, tag= 'di v ' ] ) Параметр tag указывает имя тега, в который будет «обернуто» помещаемое содер­ жимое. Вот код, выводящий то же содержимое, что и ранее приведенный пример: ui.html('<hl class="text-hl">Python</hl>' ' <р class="q-mt-sm">Язык программирования</р>' )\ .classes('text-white bg-dark') 17.4.2. Указание стилевых классов и встроенных СSS-стилей Все классы, представляющие элементы страницы, поддерживают два полезных метода:
Часть 292 ♦ classes (<стилевые стилевые классы>) - 111. Практическое Руthоп-программирование определяет для текущего элемента указанные классы, которые задаются в виде строки и разделяются пробелами. Примеры использования этого метода неоднократно приводились в тексте урока. Разнообразные стилевые классы, доступные для указания, вместе с их описа­ ниями можно найти в документации к библиотекам ♦ style (<встроенный стиль>) - Quasar 1 и Tailwind2; задает для текущего элемента указанный встроенный стиль: ui.lаЬеl('Надпись') 17.4.3. NiceGUI .style('font-size: large; font-weight: bold; ') Обработка событий DOM позволяет обрабатывать любые события DOM, возникающие в созданных элементах страницы. Обработчик события с указанным именем задается вызовом метода on (), поддерживаемого всеми элементами страницы: оn(<имя события>, Имя <ссылка на обработчик>[, throttle=O.O]) события должно быть записано в виде строки и соответствовать стандарту DOM. Обработчик может быть как функцией, так и сопрограммой. Параметр throttle задает минимальный промежуток времени, который должен пройти между последовательными вызовами задаваемого обработчика, в виде веще­ ственного числа в секундах. Этот параметр следует указать при обработке часто возникающих событий (наподобие mousernove), чтобы снизить нагрузку на процес­ сор. Пример: ui. label ( 'Проведите надо мной курсором МЬШIИ' ) \ . on ( 'mousernove', mouse_ move _ handler, throttle=l. 17.5. Статические и выгруженные файлы Статический файл 11 О) файл, передаваемый приложением веб-обозревателю как есть, без каких бы то ни было изменений и целиком. В качестве статических оформляются обычно файлы с изображениями, используе­ мые в качестве элементов оформления страниц. NiceGUI ведет перечень папок со статическими файлами, используемыми в прило­ жении. Изначально этот перечень пуст. Добавить в него папку с заданным путем и связать ее с указанным префиксом интернет-пути можно вызовом функции add static_files () ИЗ модуля nicegui.app: add_static_files(<пpeфикc интернет-пути>, <путь к папке>[, rnax_cache_age=3600]) 1 Ьttps://quasar.dev/docs. 2 Ьttps://v3.tailwindcss.com/docs.
Урок 17. Разработка веб-приложений, часть 293 1 После этого все файлы, у которых интернет-пути начинаются с заданного префикса, будут искаться в папке с указанным путем. Статические файлы кешируются веб-обозревателем. Задать время кеширования в секундах можно в параметре max_ cache _ age. Пример: from nicegui import ui, арр app.add_static_files('/static', 'c:/webapps/rnywebapp/static') ui.irnage('/static/python-logo.png') Выгруженный файл 11 файл, выгруженный в приложение пользователем. В отличие от статического файла, передается веб-обозревателю чанка.ми (по частям). Передача выгруженных файлов чанками позволяет сократить объем оперативной памяти под их промежуточное хранение и в случае аудио- или видеофайлов быст­ рее начать их воспроизведение. В NiceGUI существует отдельный перечень папок, отведенных под хранение выгруженных файлов. Добавить в него новую папку с заданным путем, связав ее с указанным префиксом интернет-пути, можно функцией add_ rnedia _ files () из моду­ ля nicegui. арр: add_media_files(<пpeфикc интернет-пути>, <путь к папке>) Пример: app.add_media files('/uploaded', 'c:/webapps/mywebapp/uploads') ui.video('/uploaded/videos/video-greeting.mp4') 17 .6. Веб-приложение фотогалереи, версия 1.0 Для закрепления изученного материала напишем простое веб-приложение фотога­ лереи. На главной странице оно будет выводить перечень миниатюр имеющихся изображений. А при щелчке на миниатюре будет выведена страница с полнораз­ мерным изображением. Ради простоты наша фотогалерея будет поддерживать лишь файлы формата а в качестве миниатюр будут выводиться изначальные изображения, JPEG, только в уменьшенном размере 1 . Создадим где-либо папку, в которой будет храниться код программы, дав ей произ­ вольное имя. В этой папке создадим папку uploaded для хранения файлов с изобра­ жениями. 1 Такой подход следует применять лишь в учебных неб-приложениях. В профессиональных фотогалереях в качестве миниаnор следует выводить автоматически генерируемые файлы с уменьшенными копиями изо­ бражений - для ускорения загрузки главной страницы.
Часть 294 111. Практическое Руthоп-программирование Из папки 17\!sources сопровождающего книгу файлового архива ( см. прwюжение в папку с кодом программы скопируем модуль data.py, 4) содержащий перечень изо­ бражений, которые будут выводиться фотоrалереей. Из той же папки файлового архива скопируем в только что созданную папку uploaded файлы с самими изобра­ жениями. Откроем модуль data.py и посмотрим на хранящийся в нем код: images = ( 'title': 'Роза', 'desc': 'Осенняя', 'filename': '1756807087. 0204823. jpg' }, Как видим, перечень опубликованных изображений организован в виде кортежа images, содержащего словари, которые хранят сведения об отдельных изображени­ ях. Каждый словарь содержит элементы title (название изображения), desc (опи­ сание, может отсутствовать) и filename (имя файла с изображением). Весь код веб-приложения (листинг 17.5) вследствие его простоты сохраним в стар­ товом модуле арр.ру. from nicegui import арр, ui from os.path import dirname, join from data import images app.add_media files('/uploaded', join(dirname( file ), 'uploaded')) @ui.page('/', titlе='Главная :: Фотогалерея') def main (): wi th ui. element ( 'main') \ .classes('full-width text-center q-col-gutter-md'): ui. label ( 'Фотогалерея') . classes ( 'text-hl') for image in images: with ui.link(", f'/(image['filename'])'): ui.label(image['title']) .classes('text-hЗ q-mt-lg') ui. image ( f' /uploaded/ ( image [ 'filename'] ) ') \ .classes ( 'w-1/3 q-mt-sm') @ui.page('/(filename)', titlе='Изображение Фотогалерея' ) def details(filename: str): with ui. element ( 'main') \ .classes('full-width text-center q-col-gutter-md'): ui.lаЬеl('Фотогалерея') .classes('text-hl') # 1 # 2 # 3 # 4 # # # 5 6 7 8 # # 9 # 10
Урок 17. Разработка веб-припожений, часть 1 295 found = [image for image in images \ if image['filename'] filename] if len(found) == 1: image = found[O] ui.label(image['title']) .classes('text-hЗ q-mt-lg') if 'desc' in image: ui. label ( image [ 'desc' ] ) . classes ( 'q-mЬ-md' ) ui. image ( f' /uploaded/ {image [ 'filename'] 1 '1 \ .classes ( 'w-5/6 q-mt-sm') else: ui.lаЬеl('Выбранное изображение ui.link('Ha главную', # 11 # 12 # 13 # 14 # 15 # 16 # 17 отсутствует.') '/') ui. run () Импортируем кортеж images из модуля data.py (поз. 1 в листинге 17.5). Папку uploaded, содержащую файлы с изображениями, мы добавим в перечень па­ пок, содержащих выгруженные файлы. Для этого нам необходимо узнать полный путь к этой папке. Мы получаем путь к текущему модулю из автоматически соз­ данной переменной _file_ (см.разд. извлекаем из него путь к папке, в кото­ 8.3), рой хранится этот модуль, вызвав функцию dirname () из модуля os. path, и объеди­ няем его с именем папки uploaded с помощью функции j oin ( J из того же модуля. Получившийся путь передаем функции add_media_files () (см.разд. 17.5 - поз. 2). Объявляем функцию main ( J, которая создаст главную страницу с перечнем изобра­ жений, связав ее с шаблонным путем / (слеш - поз. 3 ). Все содержимое главной страницы помещаем в тег <main> со стилевыми классами full -width (растягивает элемент на всю ширину родителя), text-center (выравнивает содержимое элемента по центру) и q-col-gutter-md (задает средних размеров вертикальный просвет меж­ ду потомками тру NiceGUI - поз. 4 ). К сожалению, иного способа выровнять элементы по цен­ не предлагает ... Далее перебираем кортеж images (поз. 5), создаем rиперссылку с интернет-путем формата /<имя графического файла> (поз. 6), в нее помещаем сначала надпись с названием изображения и стилевыми классами text-hЗ (шрифт с увеличенным кеглем) и q-mt-lg (большой внешний просвет сверху - поз. 7), а потом - изобра­ жение, выводящее файл, который находится по пути формата /uploaded/<uмя фай­ ла>, со стилевыми классами w-1/3 (ширина, равная (небольшой внешний просвет сверху - поз. 1/ 3 ширины родителя) и q-mt-sm 8). Функцию ctetails ( J, реализующую страницу с выбранным изображением, связыва­ ем с шаблонным путем вида /<имя файла>, задав в шаблонном пути URL-параметр filename (поз. ctetails () 9). Одноименный параметр укажем в объявлении самой функции (поз. 1О). Все содержимое страницы также помещаем в тег <main> с теми же стилевыми классами, что применялись в одноименном теге главной страницы. Для поиска изображения, хранящегося в файле, имя которого было передано в URL-параметре filename, используем списковое включение (поз. 11). Далее про-
Часть 296 111. Практическое Руthоп-программирование веряем, содержит ли результирующий список один элемент, т. е. было ли найдено изображение (поз. 12). Если это так, выводим название изображения (поз. сание, если оно указано (поз. 14), и само изображение (поз. 15). 13), опи­ Стилевой класс q-mЬ-md задает средний внешний просвет снизу, а стилевой класс w-5/6 5/ 6 от ширины родителя. ширину, равную Если изображение не было найдено, выводим соответствующую надпись (поз. В любом случае выводим гиперссылку на главную страницу (поз. 16). 17). Запустим приложение, дождемся открытия главной страницы и посмотрим на нее (рис. 17.16). Щелкнем на какой-либо миниатюре и посмотрим на появившуюся страницу с полноразмерным изображением (рис. 17.17). Фото галерея Роза Фото галерея Жешыйли_ст на асфальте Рис. 17.16. Веб-приложение фотогалереи, Рис. 17.17. Веб-приложение фотогалереи, страница с выбранным изображением главная страница Написанная нами фотогалерея совсем проста. Она позволяет лишь просматривать изображения, сведения о которых записаны в ее программном коде. Чтобы доба­ вить в галерею новое изображение, придется исправлять ее код. На текущий мо­ мент этого достаточно 17.7. ♦ ... Что еще нужно знать о NiceGUI, часть 1 Для навигации по страницам можно использовать следующие функции из моду­ ля nicegui. ui. navigate: • back () - • forward () - • возврат на предыдущую страницу в списке истории; переход на следующую страницу в списке истории; tо(<указание записывается разд. на целевую в том же 17.2.3): ui.navigate.to('/about') страницу>) формате, - переход на целевую страницу. Указание что и в вызове функции link () (см.
Урок ♦ 17. Разработка веб-приложений, часть 297 1 В модуле nicegui. ui. download объявлены функции для загрузки заданного файла с сохранением на локальном диске: загружает и сохраняет файл с заданным локальным путем; • file () - • from url () - загружает и сохраняет файл с заданным интернет-адресом. Формат вызова обеих этих функций одинаков: filelfrom_url(<лoкaльный путь файла>l<интернет-адрес файла>[, <имя файла для сохранения>=Nоnе] [, media _ t уре=' ' ]) Если имя файла для сохранения не указано, файл будет сохранен под своим изна­ чальным именем. В параметре media _ t уре указывается строка с МIМЕ-типом загружаемого файла. Если он не указан, МIМЕ-тип файла будет определен по его расширению. ♦ Функция page _ ti tle ( <новый текст названия>) заменяет текущий текст названия страницы заданным новым. from nicegui import ui ui.page_title('Изoбpaжeниe ♦ :: Фотогалерея') У любого элемента страницы можно создать всплывающую подсказку с задан­ ным текстом, вызвав у него метод ui. label ( 'Фотогалерея') . tool tip ♦ Библиотека • NiceGUI FastAPI - • VueJS - tool tip ( <текст ('Это подсказки>): заголовок веб-страницы') основана на следующих более простых библиотеках: реализует внутреннюю логику приложения (бэкенд); упрощает разработку интерфейса веб-приложений (фронтенда), позволяя составлять его из набора независимых блоков • Tailwind - - компонентов; предоставляет готовые СSS-стили для оформления страниц; • Quasar - содержит набор готовых компонентов, написанных с применением VueJS и Tailwind: спойлер, главное меню, всплывающее меню, всплывающее сообщение, диалоговое окно и многое другое. 17 .8. Самостоятельные упражнения Реализуйте в веб-приложении фотогалереи: ♦ загрузку и сохранение выбранного изображения на локальном диске. Используйте для этого обработку события click (см.разд. ♦ 17.1.5); вывод в составе названия страницы с выбранным изображением названия этого изображения; ♦ страницу со сведениями о приложении и правах его разработчиков. Когда будете писать код, выводящий эту страницу, учтите то, что сказано в кон­ це разд. 17.1.4.
Урок18 Работа с базами данных Данные веб-приложений можно хранить в струкrурах Python: списках, кортежах, словарях или объектах специально написанных классов. Однако хранящиеся там данные сразу исчезнут после того, как работа приложения будет остановлена. Чтобы данные не пропали, их следует записать на диск. Лучше всего - в базу дан­ ных. 18.1. Введение в реляционные базы данных Наиболее часто применяются реляционные базы данных, в которых данные хранят­ ся в связанных друг с другом таблицах. Отдельная строка такой таблицы хранит сведения об одной сущности (например, изображении, опубликованном в фотога­ лерее ), а столбцы этой строки содержат отдельные значения, входящие в состав этой сущности (название, описание изображения и имя хранящего его файла). Строки таблицы называются записями, а столбцы - полями. Набор полей в таблице задается при ее создании и в процессе работы не может быть изменен. Каждому полю при создании даются уникальное имя, тип хранимых им значений (строковый, целочисленный, вещественный, логический и др.) и неко­ торые дополнительные настройки: значение по умолчанию, признак, является ли поле обязательным к заполнению, и пр. Одно из полей таблицы отводится под хранение какого-либо уникального значения, однозначно идентифицирующего запись. Это значение называется ключом, а хра­ нящее его поле ключевым. В качестве ключей часто используются целые числа, - последовательно увеличивающиеся на 1 (инкрементирующиеся). Ключевое поле, содержащее такие числа, носит название автоинкрементного. После создания таблиц можно добавлять в них записи, выполнять поиск нужной записи по значениям, хранящимся в ее полях (например, по ключу), фwrьтрацию и сортировку записей по значениям указанных полей, исправлять значения полей найденной записи и удалять записи. Для ускорения поиска, фильтрации и сортировки записей в таблице применяются индексы. Индекс - это своего рода список кортежей, каждый из которых содержит два элемента: значение поля, заданного при создании индекса (называемого индек­ сированным), и ссылку на запись, в которой хранится это значение. При проведе­ нии, например, поиска по индексированному полю база данных быстро отыскивает заданное пользователем значение в компактном индексе и так же быстро извлекает
Урок 1В. Работа с базами данных 299 саму запись по взятой из него ссылке. Если поиск, фильтрация или сортировка час­ то проводятся на основе значений нескольких полей, можно создать составной, или компаундный, индекс, основанный на значениях всех этих полей. На основе ключевого поля всегда создается индекс. Он носит название ключевого. Ранее говорилось, что таблицы в реляционной базе данных зачастую связаны друг с другом. Чаще всего создается межтабличная связь вида «одна со многи:wи» - одна запись одной таблицы (первичной) связывается с произвольным количеством запи­ сей другой (вторичной) таблицы. Для этого во вторичной таблице создается поле, хранящее ключ связываемой записи первичной таблицы (поле внешнего ключа), и по нему формируется индекс. Схема 11 набор описаний таблиц, имеющихся в них полей, индексов и связей между ними. Стандартная библиотека Python уже содержит средства для взаимодействия с база­ ми данных популярного формата SQLite. Кроме того, можно установить дополни­ тельные библиотеки, обеспечивающие поддержку баз данных других форматов: PostgreSQL, MySQL, Orac\e, Microsoft SQL Server и 18.2. Библиотека многих других. Tortoise ORM: удобная работа с базами данных Для работы с базами данных удобно применять дополнительную библиотеку Tortoise ОRМ. Она обеспечивает взаимодействие с базами данных разных форма­ тов посредством одних и тех же инструментов и представляет таблицы и записи баз данных в виде обычных объектов Python. К тому же библиотека Tortoise ORM пол­ ностью конкурентна, что будет большим подспорьем при программировании веб­ приложений. Для установки версии SQLite достаточно 0.25.* этой библиотеки, описываемой в книге, с поддержкой подать команду: pip install "tortoise-onn==0.25" На заметку Полное описание библиотеки Tor1oise ORM доступно по интернет-адресу: https://tortoise.github.io/. 18.3. Модель Модели - класс, представляющий заданную таблицу в базе данных. Объект класса­ модели представляет одну из записей этой таблицы. Обслуживаемая таблица - таблица в базе данных, представляемая моделью.
300 Часть 111. Практическое Руthоп-программирование Объявление моделей 18.3.1. Объявления классов моделей рекомендуется помещать в отдельный модуль - для удобства. Обычно этому модулю дают имя models.py. Класс модели Tortoise ORM ного в модуле tortoise.rnodels. должен быть производным от класса Model, объявлен­ В классе модели следует описать поля, содержащиеся в обслуживаемой таблице. На каждое поле создается отдельный классовый атрибут, имя которого должно совпадать с именем поля. Этому атрибуту присваивается объект особого класса, содержащий описание поля. Класс, на основе которого создается объект поля, определяет тип поля: строковое, целочисленное, вещественное и т. п. Если ключевое поле в модели не было объявлено явно, библиотека сама добавит автоинкрементное поле с именем id, сделает его ключевым и создаст по нему клю­ чевой индекс. Пример простой модели, предназначенной для хранения товаров в интернет­ магазине, с четырьмя полями (не считая автоматически созданного ключевого): title (название товара, строковое, ограниченной длины - до 255 символов, может desc (текстовое, неограниченной длины), price (вещественное) и in_stock (логическое, значение по умолчанию - True): хранить лишь уникальные значения), frorn tortoise.rnodels irnport Model frorn tortoise irnport fields class Good(Model): title = fields.CharField(rnax length=255, unique=True) desc = fields.TextField() price = fields.FloatField() in_stock = fields.BooleanField(default=True) После объявления моделей следует установить соединение с базой данных и сгене­ рировать схему. В процессе генерирования схемы библиотека проверит, имеются ли в базе данных таблицы, обслуживаемые объявленными моделями, и содержатся ли в них необходимые индексы и связи. Если подходящих таблиц нет, они будут созданы автоматически. 18.3.2. Настройки, поддерживаемые всеми полями Настройки создаваемых полей указываются в именованных параметрах конструк­ торов соответствующих классов (как было показано в примере в разд. 18.3.1). Ряд настроек поддерживается полями всех типов: ♦ default - значение по умолчанию, автоматически заносимое в поле, если ника­ кого другого значения ему не было дано (по умолчанию: None); ♦ null - если True, поле станет необязательным для заполнения, если False, в по­ ле обязательно должно быть занесено какое-либо значение (по умолчанию: False);
Урок ♦ 1В. Работа с базами данных если тrue, на основе этого поля будет создан индекс, если False или dЬ _ index - не будет (по умолчанию: None); None ♦ 301 unique - если True, в поле может быть занесено лишь уникальное значение. За­ несение в это поле значения, уже имеющегося в том же поле другой записи той же таблицы, вызовет ошибку. Если False, в поле можно заносить повторяющие­ ся значения. По умолчанию: False. При присваивании параметру unique значения True на основе этого поля будет создан индекс особого вида, называемый уникальным; ♦ если тrue, поле станет ключевым, если False или None - primary_ key - нет (по умолчанию: None). Объявлять ключевое поле явно необходимо лишь в том случае, если оно отно­ сится к типу, не являющемуся автоинкрементным (например, строковому). Если ключевое поле в модели не объявлено, его создаст сама библиотека, дав ему имя id и автоинкрементный целочисленный тип; ♦ имя соответствующего поля в обслуживаемой таблице, пред­ source_field - ставленное строкой. Если не указано, имя поля будет совпадать с именем пред­ ставляющего его классового атрибута. 18.3.3. Типы полей Поля разных типов представляются классами, объявленными в модуле tortoise. fields. Вот эти классы и соответствующие им типы значений: ♦ строка ограниченной длины. Конструктор класса поддерживает CharField - дополнительный параметр max _ length, задающий предельную длину хранимой строки; ♦ ♦ тextField - 16-разрядное знаковое целое число. Представляется значением smallintField типа ♦ строка неограниченной длины; int; IntField - 32-разрядное знаковое целое число. Представляется значением типа int; ♦ 64-разрядное знаковое целое число. Представляется значением BigintField типа int; вещественное число; ♦ FloatField - ♦ BooleanField - ♦ DatetimeField - логическая величина; временная отметка (значение даты и времени) типа datetime из модуля datetime стандартной библиотеки. Конструктор класса поддерживает дополнительные параметры: • auto_now_add - при создании записи в это поле будет занесено значение те­ кущих даты и времени;
302 • Часть auto _ now - то же самое, что и 111. Практическое Руthоп-программирование auto _ now_add, только текущие дата и время будут заноситься в поле также и при каждой правке записи. Пример: class Good(Model): created_at = fields.DatetimeField(auto_now_add=True) ♦ ♦ DateField - значение даты типа date из модуля datetime стандартной библиотеки; TimeField - значение времени типа time из модуля datetime стандартной биб­ лиотеки; ♦ TimeDeltaField - промежуток времени типа timedelta из модуля datetime стан­ дартной библиотеки; ♦ BinaryField - ♦ DecimalField - последовательность байтов типа bytes; вещественное число фиксированной точности, представленное типом Decimal из модуля decimal стандартной библиотеки. Конструктор класса поддерживает дополнительные параметры: предельное количество цифр в хранимом числе; • max_digits - • decimal _places - предельное количество цифр в дробной части хранимого числа. Пример: class Good(Model): price = fields.Decima1Field(max_digits=14, decimal_places=2) ♦ JSONField словаря ♦ данные в формате Python - UUIDField - JSON. Представляется в виде либо списка, либо в зависимости от структуры сохраненных данных; универсальный уникальный идентификатор (UUID) типа uuш из модуля uuid стандартной библиотеки. Если поле такого типа используется в качестве ключевого (конструктору класса передан параметр primary_ key со значением True), то UUID для занесения в поле при создании записи будет генерироваться автоматически: class Image(Model): id = fields.UUIDField(primary_key=True) 18.3.4. Создание межтабличных связей Чтобы связать между собой две таблицы связью вида «одна со многими», доста­ точно в классе вторичной модели объявить поле внешнего ключа. Такое поле пред­ ставляется классом ForeignKeyField из модуля tortoise. fields. Конструктор этого класса поддерживает следующие параметры:
Урок ♦ 1В. 303 Работа с базами данных первый позиционный - путь к связываемой первичной модели, заданный в виде строки. Задается в формате <имя приложения>. <имя модели> ( о приложениях пой­ дет речь далее). Так, если создается связь с первичной моделью Category, объяв­ ленной в приложении ♦ related_ name - shop, следует задать путь ' shop. са tego ry' ; имя атрибута, который будет создаваться в каждом объекте записи первичной модели для хранения набора связанных записей вторичной модели, в виде строки; ♦ on_ delete - поведение библиотеки при попытке удаления записи первичной модели, с которой связаны какие-либо записи вторичной модели. Указывается в виде элементов глобального перечисления (см. разд. 10.5) из модуля tortoise. fields: CASCADE (удаление как самой записи первичной модели, так и связанных с ней записей вторичной модели) или RESTRICT (запрет удаления записи первичной модели). Значение по умолчанию: CASCADE; ♦ to_field - имя ключевого поля первичной модели в виде строки (по умолча­ нию: 'id '). Пример: class Category(Model): name = fields.CharField(rnax length=40) class Good(Model): category = fields.ForeignKeyField('shop.Category', related_name='goods', on delete=fields.RESTRICT) 18.3.5. Настройки самой модели Также можно указать настройки самой модели, записав их во вложенном классе Meta, в следующих классовых атрибутах: ♦ ordering - порядок сортировки, применяемый к записям модели по умолчанию, если при их выборке не был задан иной порядок. Указывается в виде последова­ тельности из имен полей, по которым будет выполняться сортировка. По умолчанию сортировка производится по возрастанию значения поля. Чтобы задать сортировку по убыванию значений, имя поля следует предварить зна­ ком - (минус). Пример задания сортировки по полям ti tle и price. Сначала записи будут сор­ тироваться по возрастанию значений поля title. Если у какого-то набора запи­ сей это поле хранит одинаковые значения, сортировка будет производиться по убыванию значений поля price: class Good(Model): class Meta: ordering ('title', '-price')
Часть 304 ♦ 111. Практическое Руthоп-программирование перечень составных индексов, которые должны присутствовать в об­ indexes - служиваемой таблице. Задается в виде кортежа из вложенных кортежей, каждый из которых представляет один из составных индексов и содержит имена полей, включенных в этот индекс. Пример модели, содержащей два составных индекса: class Good(Model): class Meta: indexes = (('title', 'price'), ('title', 'category')) Вместо кортежей можно использовать списки; ♦ unique _ together - перечень составных индексов, которые должны хранить уни­ кальные наборы значений. Задается в том же формате, что и перечень обычных составных индексов из атрибута indexes; ♦ tаЫе - имя обслуживаемой таблицы в виде строки. Если не задано, библиотека сама сгенерирует имя таблицы согласно правилам, описанным далее. Пример двух связанных моделей: >>> frorn tortoise.rnodels irnport Model >>> frorn tortoise irnport fields >>>#Модель категорий товаров >>> class Category(Model): narne = fields.CharField(rnax length=40, unique=True) class Meta: ordering = 'narne', >>>#Модель самих товаров >>> class Good(Model): title = fields.CharField(rnax length=SO, dЬ index=True) desc = fields.TextField() price = fields.FloatField() category fields.ForeignKeyField('shop.Category', related_narne='goods', on_delete=fields.RESTRICT) in stock = fields.BooleanField(default=True) class Meta: ordering = ('title', '-price') indexes = (('title', 'in_stock'),) 18.4. Соединение с базой данных и инициализация схемы После объявления всех моделей необходимо: ♦ установить соединение с выбранной базой данных граммы-метода ini t () у класса Tortoise из модуля - вызовом классовой сопро­ tortoise:
Урок 18. 305 Работа с базами данных init(dЬ_url=<интepнeт-aдpec базы данных>, mоdulеs=<конфи.тwация моделей>) Интернет-адрес базы данных задается в виде строки с обозначением протокола, соответствующего формату выбранной базы данных. Так, при соединении с ба­ зой данных SQLite следует указать обозначение протокола sqlite:. Конфи.rwация моделей должна представлять собой словарь с единственным эле­ ментом. В качестве ключа этого элемента следует указать имя приложения, которое можно выбрать произвольно и которое должно совпадать с именем, заданным при создании межтабличных связей (см. разд. 18.3.4). Значением эле­ мента должен быть список с путями к модулям, в которых объявлены модели; ♦ сгенерировать схему данных - и связями - все необходимые таблицы с полями, индексами Rызовом классовой сопрограммы-метода generate_ schemas () у того же класса. Пример: >>> from tortoise import Tortoise >>> from asyncio import run >>> async def dЬ_init(): # Поскольку мы объявили модели в интерактивной оболочке, # в качестве пути к модулю с моделями указываем строку '_main await Tortoise.init( dЬ_url='sqlite:///c:/webapps/mywebapp/data/data.sqlite', modules={ 'shop': (' main ']}) await Tortoise.generate_schemas() >>> run(dЬ_init()) База данных 18.5. SQLite будет создана при первой попытке обращения к ней. Запись данных Под записью данных будем понимать как создание новых записей, так и правку и удаление существующих. 18.5.1. Создание записей Создать новую запись в таблице посредством обслуживающей ее модели можно двумя способами: ♦ создать объект модели, представляющий новую запись, вызвав ее конструктор в формате: <класс модели>(<поле ]>=<значение поля <поле 2>=<значение поля 1>, 2>, . . ., <поле N>=<значение поля N>)
306 Часть 111. Практическое Руthоп-программирование Значения полей создаваемой записи указываются в параметрах конструктора, имена которых совпадают с именами этих полей. Созданную запись потом следует сохранить в таблице, вызвав у ее объекта со­ программу-метод save () . Пример создания новой категории товаров: >>> async def f(): с= Саtеgоrу(nаmе='Предметы обихода') await с. save () >>> run ( f ( ) ) ♦ использовать классовую сопрограмму-метод create () модели. Значения полей создаваемой записи, как и ранее, передаются в именованных параметрах этой сопрограммы. Сопрограмма сразу же сохраняет созданную запись в таблице и возвращает фьючер с объектом этой записи. Пример: >>> async def f(I: await Category.create(name='Бытoвaя техника') # Ищем в базе данных только что добавленные категории no их # ключам - 1 и 2 соответственно. Для этого используем # метод get () модели, описывамый далее. cl = await Category.get(id=l) с2 = await Category.get(id=2) # При создании товаров не забываем указать у них категории await Good.create(title='Wилo', desc='Xopoшee, острое', price=l00, category=cl) await Good.create(title='Yтюг', desc='C парогенератором', price=S000, category=c2) await Good.create(title='Cтиpaльнaя машина', dеsс='Автомат', price=40000, category=c2, in_stock=False) await Good.create(title='Mыno', dеsс='Хозяйственное', price=B0, category=cl) await Good.create(title='Oпилки', desc='', price=0, category=cl) >» run ( f ( 1 1 18.5.2. Правка записей Для правки записи следует найти эту запись, занести в ее поля новые значения, присвоив их одноименным атрибутам объекта-записи, и сохранить вызовом сопро­ граммы-метода save ( 1.
Урок 307 18. Работа с базами данных 100 рублей за шило - многовато ... Давайте сбавим цену до 50: >>> async def f(): g = await Good.get(title='Шилo') g.price = 50 await g. save () »> run(f()) 18.5.3. Удаление записей Удалить запись можно, вызвав у ее объекта сопрограмму-метод delete () . Для при­ мера удалим из состава товаров опилки: >>> async def f(): g = await Good.get(title='Oпилки') await g.delete() >>> run ( f ()) 18.6. Выборка данных 18.6.1. Поиск записи Для поиска единственной записи по заданным условиям применяется классовая сопрограмма-метод get (<условия поиска>) модели. Условия поиска задаются в виде набора обычных именованных параметров формата: <имя поля>=<значение, которое должно содержаться в этом поле> Сопрограмма возвращает фьючер с объектом найденной записи. Если подходящая запись не нашлась, возбуждается исключение типа DoesNotExist из модуля tortoise. exceptions. Если же нашлось несколько подходящих записей, возбужда­ MultipleObjectsReturned из того же модуля. ется исключение типа Для примера найдем товар с ценой 40 тыс. рублей: >>> async def f(): g = await Good.get(price=40000) print(g.title) >» run ( f () ) Стиральная машина Проверим, продаются ли у нас чайники: >>> async def f(): g = await Good.get(title='Чaйник')
Часть///. Практическое Руthоп-программирование 308 >>> run(f()) Traceback (most recent call last): tortoise.exceptions.DoesNotExist: Object "Good" does not exist 18.6.2. Извлечение значений из полей записи Значения полей найденной записи хранятся в одноименных им атрибутах объекта модели, представляющего эту запись. Найдем товар с ключом 2и посмотрим на значения его полей: >>> async def f(): g = await Good.get(id=2) print(g.pk, g.title, g.desc, g.price, sep=' 1 ') >>> run(f()) 2 1 Утюг I С парогенератором 1 5000.0 Атрибут pk хранит ключ записи. 18.6.3. Фильтрация записей. Наборы записей Для фильтрации записей по заданным значениям их полей следует применять клас­ совый метод f il ter ( <условия филмрации>) модели. Условия фильтрации задаются в том же формате, что и условия поиска в вызове сопрограммы-метода get () (см. разд. 18.6.1). Метод возвращает набор записей, содержащий все отфильтрованные записи. Набор записей 11 содержит произвольное количество записей, полученных в резуль­ тате фильтрации, сортировки и др. Набор записей имеет функциональность фьючера и асинхронного итератора. Сле­ довательно, разд. к 14.1.1) - нему можно применить как оператор приостановки await (см. для получения обычного списка с объектами записей, так и цикл перебора с приостановкой (см. разд. 14.1.2.1), в котором на каждой и·,·ерации будет выдаваться очередная запись. Набор записей также поддерживает метод get ( ) , описанный в разд. 18. 6.1, и ряд других методов, рассматриваемых далее. Набор записей представляется объектом класса QuerySet из модуля tortoise. queryset. Выберем все товары, имеющиеся в наличии в магазине (у которых поле in stock хранит значение тrue), применив оператор приостановки и перебрав полученный список записей в обычном цикле перебора: >>> async def f(): gs = await Good.filter(in_stock=True) for g in gs: print(g.title, end=', ')
Урок 309 1В. Работа с базами данных >>> run(f()) Мыnо, Утюг, Шило, Выберем все товары с ценой более 1О тыс. рублей, на этот раз использовав цикл перебора с приостановкой: >>> async def f(): async for g in Good.filter(price_gt=lOOOO): print(g.title, end=', ') »> run ( f () ) Стиральная машина, Можно сцепить произвольное количество вызовов метода filter () - и заданные в них условия будут объединены по правилам логического И. Например, так можно найти все товары с ценой более 1О тыс. рублей И имеющиеся в наличии: gs = await Good.filter(price_gt=lOOOO) .filter(in_stock=True) Хотя проще описать все необходимые условия в одном вызове метода: gs = await Good.filter(price_gt=lOOOO, in stock=True) Метод exclude () подобен методу fil ter (), только выдает набор со всеми записями, за исключением удовлетворяющих заданным условиям. Так, следующее выражение выдаст товары, наоборот, отсутствующие в продаже: gs = await Good.exclude(in_stock=True) 18.6.3.1. Модификаторы Если в вызове метода fil ter () указать условие вида <имя поля>=<значение>, то биб­ лиотека выберет все записи, у которых поле с заданным именем хранит указанное значение, причем в случае строковых и текстовых полей сравнение будет произво­ диться с учетом регистра символов. Но что делать, если требуется выбрать записи, у которых содержимое заданного поля меньше или больше указанного значения, или произвести сравнение без учета регистра? Использовать модификаторы. Модификатор - указывает, как при поиске и фильтрации библиотека должна вы­ полнять сравнение значения поля с заданным значением. Записывается после имени поля и отделяется от него двойным подчеркиванием. Пример использования модификатора gt, который предписывает искать записи, у которых указанное поле хранит значения, большие, чем указанные, приведен в разд. 18.6.3. Вот он: async for g in Good.filter(price_gt=lOOOO): Доступные модификаторы приведены в табл. 18.1.
Часть 310 Практическое Руthоп-программирование ///. Таблица Модификатор 18.1. Модификаторы l Описание not Содержимое поля, наоборот, не должно совпадать с заданным значением: in_price=True in price - not=True iexact # ['Мыпо', 'Утюг', 'Шило') # [ 'Стиральная машина ' ) Содержимое поля должно совпадать с заданным значением. Регистр символов не учитывается: startswith title='шилo' # [) title # iехасt='шило' ['Шило') Содержимое поля должно начинаться с заданной подстроки. Регистр ' символов учитывается: title - startswith='Cтиpaльнaя' # [ 'Стиральная машина' ) istartswith То же самое, что и endswith Содержимое поля должно заканчиваться заданной подстрокой. Регистр startswith, только регистр символов не учитывается символов учитывается iendswith ~ То же самое, что и ------ - contains endswi th, только регистр символов не учитывается -- Содержимое поля должно содержать заданную подстроку. Регистр символов учитывается: title title - - - # [ 'Стиральная # [) contains='мaшинa' contains='Maшинa' машина') icontains То же самое, что и - title - lt Содержимое поля должно быть меньше заданного значения contains, только регистр символов не учитывается: icontains='Maшинa' # - ['Стиральная машина') - - - -- lte Содержимое поля должно быть меньше заданного значения или равно ему gt Содержимое поля должно быть больше заданного значения gte Содержимое поля должно быть больше заданного значения или равно ему range Содержимое поля должно находиться внутри заданного диапазона, который -- указывается в виде кортежа из двух элементов: начального и конечного значений диапазона. Начальное и конечное значения входят в диапазон: pr1ce - range=(l000, 10000) in Содержимое поле должно присутствовать в заданном списке: - title in= [ 'Утюг' , 'Шило', ' Полотенце' ) not in Содержимое поле не должно присутствовать в заданном списке isnull Поле должно быть пустым, если в качестве значения задано - и не пустым, если задано desc - not - isnull - False. True, Выбираем все товары без описаний: isnull=True Поле должно быть не пустым, если в качестве значения задано и пустым, если задано False True,
Урок 18. Работа с базами данных 18.6.4. 311 Сортировка записей Сортировку можно указать лишь у набора записей, возвращенного сопрограммой­ методом filter () или exclude (). Модели ее не поддерживают. Если же требуется задать сортировку у модели, следует использовать метод all () ( описан в разд. 18. 6. 7). Сортировка по полям с заданными именами задается вызовом классового метода order_by(): оrdеr_Ьу(<имя поля 1>, <имя поля 2>, . . . , <имя поля N>) Как и при задании порядка сортировки в настройках самой модели (см. разд. 18.3.5), по умолчанию сортировка выполняется по возрастанию значения поля, а для зада­ ния сортировки по убыванию следует предварить имя поля знаком «минус». Пример (с использованием метода all () ): >>> async def f(): async for g in Good.all() .order_by('in_stock', '-title'): print(g.title, g.price, g.in_stock) >» run ( f ( ) ) Стиральная машина Шило Утюг Мыло 18.6.5. 40000.0 False 50.0 True 5000.0 True ВО.О True Извлечение первой и последней записей Следующие классовые сопрограммы-методы поддерживаются и моделью, и набо­ ром записей: ♦ first () - возвращает фьючер с первой записью из модели или набора: >>> async def f(): # Сначала извлекаем первый товар из хранящихся в модели ... g = await Good.first() print(g.title) # ... а потом - самьш дешевьш товар из тех, # чья цена не превьШJает 500 рублей g = await Good.filter(price lte=500) .order_by('price')\ . first () print(g.title) » > run ( f ( ) ) Мыло Шило ♦ last () - возвращает фьючер с последней записью.
Часть 312 18.6.6. 111. Практическое Руthоп-программирование Извлечение заданного количества записей Извлечь заданное количество записей, пропустив указанное число записей с начала набора, позволят два классовых метода класса QuerySet. Модели эти методы не поддерживают, поэтому потребуется предварительно вызвать метод all () : ♦ limit (<количество извлекаемых записей>) - извлекает и возвращает из текущего набора заданное количество записей; ♦ offset (<количество пропускаемых записей>) - пропускает заданное количество записей с начала текущего набора и возвращает оставшиеся записи. Оба метода в качестве результата возвращают новый набор записей. Извлекаем три товара, пропустив один товар с начала (не забыв вызвать метод all () ): >>> async def f(): async for g in Good.all() .offset(l) print(g.title, end=', ') .limit(З): >» run(f()) Стиральная машина, 18.6.7. Утюг, Шило, Извлечение всех записей Для извлечения всех записей, содержащихся в модели, следует применять классо­ вый метод all () модели. Он возвращает набор, содержащий все записи модели. Метод all () часто применяется при необходимости выполнить с моделью дей­ ствие, не поддерживаемое ею (например, сортировку или выборку заданного коли­ чества записей): async for g in Good.all() .order_by('in_stock', '-title'): 18.6.8. Проверка существования записей Проверить, существуют ли записи в модели или наборе, позволят две классовые сопрограммы-метода: ♦ exists ( [ <условия фильтрации> J ) класса модели - возвращает фьючер со значе­ True, если в текущей модели есть записи, или False - если модель пуста: нием >>> async def f(): print(await Category.exists()) >>> run(f()) True Можно указать условия фильтрации записей в том же формате, который применя­ ется в методе f il ter () ( см. разд. более 1 млн рублей: 18. 6. 3). Проверим, имеются ли товары с ценой
Урок Работа с базами данных 18. 313 >>> async def f(): print(await Good.exists(price_gte=lOOOOOO)) >>> run(f()) False ♦ exists () набора записей аналогичен одноименному методу модели, только не - позволяет задавать условия фильтрации: expensive_goods_exist = await Good.filter(price_gte=lOOOOOO) .exists() 18.6.9. Извлечение связанных записей Библиотека Tortoise ОRМ позволяет из записи вторичной модели обращаться к свя­ занной с нею записи первичной модели, а из записи первичной модели - ко всем связанным с нею записям вторичной модели. ♦ В каждой записи вторичной модели создается атрибут, имя которого совпадает с именем связанной первичной модели, приведенным к нижнему регистру. Этот атрибут хранит набор записей QuerySet, содержащий единственную запись пер­ вичной модели, связанной с текущей записью. Так, в записи модели товара Good, связанной с моделью категории category, будет создан атрибут с именем category, хранящий связанную категорию. Извлечь единственную запись из упомянутого набора записей можно вызовом метода first() или last() (см.разд. 18.6.5) 1. Ищем товар «Шило» и выясняем категорию, к которой он относится: >>> async def f(): g = await Good.get(title='Шилo') с= await g.category.first() print(c.name) >>> run(f()) Предметы обихода ♦ В каждой записи первичной модели создается атрибут, имя которого было зада­ но при создании связи, ForeignКeyField (см.разд. в параметре 18.1.4). related_name конструктора класса Этот атрибут хранит набор связанных записей вторичной модели, представленный объектом класса ReverseRelation из модуля tortoise. fields. relational. Этот класс поддерживает методы fil ter () ( см. 18.6.3), order_by () (см. разд. 18.6.4), limit () и offset () (см. разд. 18.6.6). разд. Извлекаем все товары из категории «Предметы обихода» и выводим их отсорти­ рованными по убыванию цены: 1 Логичнее, запись. если бы упомянутый атрибут хранил не набор из одной связанной записи, а саму связанную
Часть 314 111. Практическое Руthоп-программирование >>> async def f(): с= await Category.get(name='Пpeдмeты обихода') async for g in c.goods.order_by('-price'): print(g.title, g.price) >>> run ( f ( ) ) Мьuю ВО.О Шило 50.0 18.6.1 О. Извлечение записей в виде списка словарей Набор записей представляет собой последовательность объектов модели, представ­ ляющих выбранные записи. Однако в ряде случаев удобнее получить выбранные записи в виде списка словарей. Ключи элементов каждого из таких словарей совпа­ дают с именами полей, а значения элементов суть значения этих полей. Выборку записей в виде списка словарей производит сопрограмма-метод values () набора записей: values ( [<имя поля 1>, <имя поля 2>, ... , <имя поля 3>)) В качестве параметров можно задать имена полей выбираемых записей, которые должны присутствовать в выдаваемых словарях. Если они не заданы, в словарях будут присутствовать все поля. Пример: >>> async def f(): async for g in Good.all() .values('id', 'title', 'price'): print(g) >>> run(f ()) { 'id': 4, { 'id': 3, { 'id': 2, { 'id': 1, 18. 7. 'title': 'title': 'title': 'title': 'Мыnо', 'price': 80.0} 'price': 40000.0) 'price': 5000.0} 'price': 50.0} 'Стиральная машина', 'Утюг', 'Шило', Закрытие соединения с базой данных По завершении работы с базой данных требуется закрыть соединение с ней. Это выполняется вызовом классовой сопрограммы-метода Tortoise ИЗ модуля tortoise: >>> async def dЬ_close(): await Tortoise.close connections() »> run ( f () ) close connections () класса
Урок 18. Работа с базами данных 18.8. Интеграция Библиотеки и Tortoise ORM и Tortoise ORM 315 NiceGUI NiceGUI при работе никак не «мешают» друг другу. Единственная трудность: при запуске приложения следует соединиться с базой данных, а при остановке - закрыть соединение. Но она преодолима. При запуске приложения в его объекте возникает событие оп_ startup, а при завер­ шении событие оп_ shutdown. К этим событиям можно привязать обработчики - функции или сопрограммы, - - которые будут выполнять необходимые действия. Привязка заданного обработчика к событию выполняется вызовом функции из мо­ дуля nicegui. арр, одноименной этому событию: on_startuplon_shutdown(<oбpaбoтчик>) 18.9. Веб-приложение фотогалереи, версия Приступим к написанию версии 2.0 2.0 фотогалереи. От первой версии она будет иметь два отличия. Во-первых, она станет многопользовательской. Каждое опубликованное изображе­ ние будет связано с пользователем, который его опубликовал. Таких пользователей может быть произвольное количество, а регистрацию новых пользователей в при­ ложении сделаем свободной. Во-вторых, все внутренние данные приложения пользователей и опубликованных изображений - перечни зарегистрированных будут храниться в базе данных - в отдельных таблицах, связанных между собой. Это позволит нам на уроке 19 с легкостью реализовать регистрацию новых пользователей и добавление новых изображений. Код приложения окажется довольно объемистым, поэтому будем писать его по час­ тям. 18.9.1. Модели пользователя и изображения Сначала объявим модели зарегистрированного пользователя и опубликованного изображения. Модель пользователя назовем user и добавим в нее поля, приведенные в табл. Таблица 18.2. Поля модели User Имя Тип Описание Примечание пате CharField Имя пользователя Длина 40 password CharField Хеш пароля пользователя Длина 128 1 18.2. символов, уникальное символов В поле password будем хранить не сам пароль, а хеш, вычисленный на его осно­ ве, - ради безопасности. Вычисление хешей, стойких к взлому и пригодных для представления паролей, рассмотрим на уроке 19.
Часть 316 111. Практическое Руthоп-программирование Модель изображения будет носить имя Image и содержать поля из табл. Таблица 18.3. 18.3. Поля модели Image Имя Тип Описание Примечание title CharField Название Длина desc TextField Описание Необязательное filenarne CharField Имя файла с изображением Длина user ForeignKeyField Пользователь, опубликовав- Связь с моделью 50 22 символов символа User ший изображение puЬlished DatetimeField Временная отметка Автоматическое занесение публикации текущей временн6й отметки при создании записи, индексированное Формирование имен файлов на основе отметок времени их публикации будет рас­ смотрено на уроке 19. У модели изображения укажем сортировку по умолчанию по убыванию временной отметки публикации (значения поля puЫished). В папке, где хранится код приложения, создадим папку modules, а в ней модуль - пустой _init_.py. Код, объявляющий модели, сохраним в модуле modules\models.py (листинг 18.1 ). Он не требует комментариев. 1 ЬМсntнг B- 18.1. Веб-n~~фотоrалереи, вe. . . frorn tortoise.rnodels irnport Model frorn tortoise irnport fields class User(Model): narne = fields.CharField(rnax length=40, unique=True) password = fields.CharField(rnax_length=l28) class Irnage(Model): title = fields.CharField(rnax_length=SO) desc = fields.TextField(null=True) filenarne = fields.CharField(max_length=22) user = fields.ForeignKeyField('photogallery.User', related_narne='irnages', on delete=fields.RESTRICT) puЬlished = fields.DatetirneField(auto_now_add=True, dЬ index=True) class Meta: ordering=('-puЬlished',)
Урок 18. Работа с базами данных 18.9.2. 317 База данных веб-приложения В папке с кодом приложения создадим папку data - в ней будет находиться файл с базой данных. В папке 18\!sources сопровождающего книгу файлового архива (см. приложение 4) найдем файл data.sqlite, содержащий готовую базу данных с двумя пользователями и шестью изображениями, и скопируем в только что соз­ данную папку. Напишем код, устанавливающий соединение с базой данных, инициализирующий схему и закрывающий соединение, сохранив его в модуле modules\connection.py (лис­ тинг 18.2). Листинr модуль 18.2. Веб-приложение modules\connection.py фотоrаnереи, версия 2.0, from os.path import dirname, join from tortoise import Tortoise async def c:!Ь_init(): basepath = dirname(dirname( file )) c:!Ь_path = join(basepath, 'data', 'data.sqlite') await Tortoise.init(dЬ_url='sqlite:///' + c:!Ь_path, modules=('photogallery': ['modules.models'] await Tortoise.generate_schemas() async def dЬ close(): await Tortoise.close connections() Соединение с базой dЬ ini t () ( поз. и инициализацию схемы мы производим # # 1 2 3 4 # 5 # # 1) в сопрограмме 1 в листинге 18.2). Чтобы получить путь к папке приложения, из пути к текущему модулю нам при­ дется удалить два последних сегмента: имя файла модуля connection.py и имя папки modules, в которой он хранится. Поэтому путь к текущему модулю мы «пропуска­ ем» через функцию dirname () дважды (поз. 2). Далее с помощью функции join() добавляем к полученному пути имя папки data и имя файла data.sqlite чаем полный путь к базе данных (поз. обозначение протокола sqlite и 4). 3). и полу­ К полученному пути добавляем спереди указываем в параметре c:!Ь_url сопрограммы-метода ini t () класса Tortoise (поз. Код, закрывающий соединение с базой данных, оформляем в виде сопрограммы dЬ close () (поз. 18.9.3. 5). Вывод изображений из базы данных Осталось исправить код модуля арр.ру таким образом, чтобы изображения выводи­ лись из базы данных (листинг 18.3 ).
Часть 318 111. Практическое Руthоп-программирование fram tortoise.exoeptions import DoesNotExist from data images iffijЭort fram modules.connection import dЬ_init, fram modules .models import Image dЬ close app.on_startup(dЬ_init) # 1 # 2 app.on_shutdown(dЬ_close) @ui.page('/', titlе='Главная Фотогалерея' ) async def main () : with ui. element ( 'main') \ .classes('full-width text-center q-col-gutter-md'): ui. label ( 'Фотогалерея') . classes ( 'text-hl') async for image in Image.all(): wi th ui . link ( ' ' , f ' / ( image . pk} ' ) : q-mt-lg') image.user.first() ui. laЬel (user. name) ui.image(f'/uploaded./{image.filename}')\ .classes('w-1/3 q-mt-sm') ui.laЬel(image.title) .classes('text-hЗ user = await @ui. page ( '/ {pk} ' , ti tle=' изображение : : async def details (pk: int) : Фотогалерея • ) wi th ui. element ( 'main') \ .classes('full-width text-center q-col-gutter-md'): ui. label ( 'Фотогалерея') . classes ( 'text-hl') try: image = await Image.get(pk=pk} ui.laЬel(image.title) .classes('text-hЗ 4 # # # # 7 # 8 # # 3 5 6 9 # 10 # 11 q-mt-lg') if image.desc: ui.laЬel(image.desc) ui. laЬel ( (awai t image. user. first () ) . name) . classes ( 'q-mЬ-md') ui.image(f'/uploaded/{image.filename}')\ .classes ( 'w-5/6 q-mt-sm') # 12 exoept DoesNotExist: ui. laЬel ( 'ВЪ!бранное изображение отсутС'l'Вует. ' ) ui.link('Ha главную', '/') Прежде всего, привяжем к событиям запуска on_ startup и завершения on_ shutdown веб-приложения обработчики dЬ _ ini t () и dЬ _ close () из ранее написанного модуля modules\connection.py (поз. 1 в листинге 18.3). Функции main () и details (), выводящие главную страницу и страницу с выбран­ ным изображением, преобразуем в сопрограммы (поз 2 и 3). В теле этих функций мы будем обращаться к конкурентным механизмам библиотеки Tortoise ОRМ.
Урок 18. Работа с базами данных 319 В теле сопрограммы main () извлекаем все записи из модели изображения Image и перебираем их в цикле перебора с приостановкой (поз. 4). Сортировку по убыва­ - мы указали ее в на­ нию временной отметки создания изображений не задаем стройках модели (см. листинг Для извлечения значений 18.1 ). из полей записей модели изображения к одноименным им атрибутам объектов этих записей (поз. 5, 6 обращаемся и далее в коде). Чтобы извлечь имя пользователя, опубликовавшего очередное изображение, для вывода его на странице, извлекаем из атрибута user модели набор с единственной записью, представляющей этого пользователя, first () для получения этой записи (поз. 7). и вызываем сопрограмму-метод Далее получаем из атрибута name этой записи имя пользователя и выводим его в надписи (поз. 8). Странице со сведениями об изображении теперь будем передавать ключ записи, представляющей это изображение, поскольку поиск записи по ключу выполняется быстрее, чем по имени файла. Дадим URL-параметру, с которым передается ключ, имя pk - по принятым в программировании соглашениям (поз. 9). В 3). объявлении сопрограммы details () запишем параметр с таким же именем (поз. В обработчике исключений (поз. параметре ключом (поз. 11 ). 10) ищем изображение с полученным в URL- Если оно найдено, выводим его на странице. В про­ тивном случае возбуждается исключение типа DoesNotExist, которое мы обрабаты­ ваем (поз. Модуль 12), выводя соответствующее сообщение. data.py, в котором создается список со сведениями об изображениях, больше не нужен и может быть удален. Проверим, как работает вторая версия нашего веб-приложения. 18.1 О. Что еще нужно знать о библиотеке ♦ Tortoise ORM И модель, и набор записей поддерживают сопрограмму-метод get _ or_ none (), аналогичную сопрограмме-методу get () (см. разд. 18.6.1) и вызываемую так же. Отличается она тем, что в случае, если подходящая запись не найдена, возвра­ щает значение None. Возможно, в каких-то случаях использовать эту сопрограм­ му-метод будет удобнее, чем get (). ♦ Сопрограмма-метод get _ or _ create () модели ищет запись, удовлетворяющую заданным условиям поиска, и возвращает ее. get_or_create(<ycлoвия поиска>[, defaults=None]) Если же подходящей записи не нашлось, сопрограмма создает новую запись, за­ носит в ее поля значения из условий defaults, поиска и словаря, указанного в параметре сохраняет и возвращает ее. Пример: g = await Good.get or create(title='lllвaбpa', defaults={'price': 1000))
Часть 320 ♦ 111. Практическое Руthоп-программирование Модель и набор записей поддерживают две сопрограммы-метода, которые могут оказаться полезными: • earliest () - сортирует записи по значениям полей с заданными именами и возвращает первую запись получившегося набора; • latest () - сортирует записи по значениям полей с заданными именами и воз- вращает последнюю запись получившегося набора. Формат вызова обеих сопрограмм такой же, как и у метода order_ Ьу () разд. ( см. 18.6.4). В качестве примера найдем самый дорогой товар: >>> async def f(): g = await Good.latest('price') print(g.title, g.price) >>> run(f()) Стиральная машина ♦ 40000.0 Tortoise ОRМ дополнительно предоставляет инструменты для выполнения агре­ гатных вычислений, специфической выборки данных, поля специфических типов и т. п. Все это описано в документации по библиотеке. 18.11. l. Самостоятельные упражнения Выведите на странице также временнь1е отметки публикации каждого изобра­ жения. Для вывода временной отметки в привычном формате: <число>. <номер месяца>. <.год> <часы>:<минуты>:<секунды> вызовите у объекта этой временной отметки метод strftime (), передав ему в па­ раметре строку 2. '%d. %m. %У %Н: %М: %S'. Попробуйте задать у выводимых изображений другие условия сортировки, например по возрастанию временной отметки публикации и названию изобра­ жения.
Урок19 Разработка веб-приложений, часть Возвращаемся к рассмотрению библиотеки NiceGUI, 2 предназначенной для разра­ ботки веб-приложений. 19.1. Элементы управления управления Элементы nicegui. ui 19.1.1. создаются вызовами функций, объявленных в модуле и описываемых далее. Кнопка Обычная кнопка создается функцией button (): Ьuttоn(<надпись>[, on_click=None] [, color='primary']) Надпись для кнопки задается в виде строки. В параметре on_ click указывается ссыл­ ка на функцию-обработчик события click (щелчок на кнопке). Параметр color служит для указания цвета фона у кнопки в виде стилевого класса Tailwind 2 или в стандарте Quasar', CSS. Пример: frorn nicegui irnport ui ui.Ьuttоn('Добавить', on_click=add_new_image) Кнопка, представляемая классом вutton, поддерживает свойства text и visiЫe (см. разд. 17.2.1), свойство 19.1.2. Поле ввода enaЫed, методы disaЫe () и еnаЫе () (см. разд. Обычное поле ввода создается функцией input ( ) : input ( [label=None] [, ] [placeholder=None] [, ] [value=' '] [, [password=False] [, ] [password_toggle_button=False] [, [on_change=None]) 1 https://quasar.dev/docs. 2 https://v3.tailwindcss.com/docs. 17.3.5).
Часть 322 111. Практическое Руthоп-программирование Параметры: ♦ label - текст надписи, выводимой над полем ввода; ♦ placeholder - ♦ value - ♦ password - текст, выводимый в пустом поле ввода; изначальное значение для поля ввода; если тrue, будет создано поле для ввода пароля, скрывающее все заносимые символы, если ♦ False - обычное поле ввода; password_toggle_button- если True, в правой части поля ввода появится кнопка для показа введенного пароля, если False, такой кнопки не будет; ♦ on_ change - ссылка на функцию, вызываемую при изменении занесенного в поле ввода значения (обработчик события change). Пример: ui.input(label='Имя пользователя') ui.input(label='Пapoль', Поле ввода - password=True, password_toggle_button=True) это класс Input. Он поддерживает свойство visiЫe (см.разд. свойство enaЫed, методы disaЫe 17.2.1), () и еnаЫе () (см. разд. 17.3.5), а также: ♦ value - свойство, значение, занесенное в текущее поле ввода, в виде строки; ♦ свойство, текст надписи над элементом управления. label - 19.1.3. Область редактирования Для создания области редактирования служит функция textarea () : textarea([label=None] [, ] [placeholder=None] [, ] [value=''] [, ] [on_change=None]) Назначение параметров аналогично таковым у функции input () (см.разд. 19.1.2). Пример: ui.textarea(label='Oпиcaниe изображения') Класс тextarea, представляющий область редактирования, поддерживает те же свойства и методы, что и класс 19.1.4. Input (см.разд. 19.1.2). Флажок Для создания флажка достаточно вызвать функцию checkЬox () : checkbox (<надпись> [, value=False] [, on_ change=None] ) Надпись для создаваемого флажка указывается в виде строки. Параметр value задает изначальное состояние флажка: тrue (установлен) или False (сброшен). Параметр on_ change служит для задания ссылки на функцию, вызываемую при смене состоя­ ния флажка (обработчик события change). Пример: ui.сhесkЬох('Изображение доступно для всех', value=True)
Урок 19. Разработка веб-приложений, часть 2 323 Класс Checkbox, представляющий флажок, поддерживает свойства text и visiЫe (см.разд. 17.2.1), свойство enaЫed, методы disaЫe() и еnаЫе() (см.разд. 17.3.5). Помимо того, он предоставляет свойство value, хранящее текущее состояние флаж­ ка: True (флажок установлен) или False (сброшен). 19.1.5. Набор переключателей Набор переключателей создается вызовом функции radio (): radio ( <описание переключателей> [, value=None) [, on_ change=None]) Описание переключателей можно указать в виде: ♦ списка тогда его элементы будут использованы в качестве как надписей у от­ - дельных переключателей набора, так и их значений; ♦ словаря - тогда ключи его элементов будут использованы в качестве значений переключателей, а значения элементов - в качестве надписей у них. Параметр value задает значение переключателя, установленного изначально. Если задать значение None, ни один переключатель не будет установлен. Параметр on_ change указывает ссылку на функцию, которая будет вызвана при установке ка­ кого-либо переключателя набора (обработчик события change). Пример: radios = { 'm': 'Мужской', 'f': ui. label ( 'Укажите ваш пол: ' ) ui.radio(radios, value='m'} 'Женский'} Набор переключателей, представляемый классом Radio, поддерживает свойство visiЫe (см. разд. разд. ♦ 17.2.1), свойство enaЫed, методы disaЫe() и еnаЫе() (см. 17.3.5), а также: свойство, значение переключателя, установленного в настоящий мо­ value мент; ♦ set _ options () - метод, задает новые переключатели для текущего набора: set_options(<oпиcaниe новых переключателей>[, value=None)) Описание новых переключателей указывается в том же формате, а параметр value имеет то же назначение, что и в вызове функции radio () . 19.1.6. Раскрывающийся список Раскрывающийся список формирует функция select (): sеlесt(<описание пунктов>[, label=None) [, value=None) [, on_change=None) [, with_input=False) [, multiple=False)) Описание пунктов можно указать в виде: ♦ списка - тогда его элементы будут использованы в качестве как надписей на пунктах списка, так и их значений;
Часть 324 ♦ словаря 111. Практическое Руthоп-программирование тогда ключи его элементов будут использованы в качестве значений - пунктов, а значения элементов - в качестве надписей на них. Остальные параметры: ♦ текст надписи, которая будет выводиться над списком; label - ♦ value - значение пункта, который должен быть выбран изначально. Если задать значение None, ни один пункт не будет выбран; ♦ ссылка на функцию, вызываемую при выборе в списке другого on_ change - пункта ( обработчик события change ); ♦ wi th_ input - если True, в верхней части списка появится поле ввода для фильт­ рации пунктов списка по надписям на них, если False, такого поля ввода в спи­ ске не будет; ♦ multiple - если True, в списке можно будет выбрать произвольное количество пунктов, если False - лишь один пункт. Пример списка с возможностью выбора лишь одного пункта и фильтрации пунктов по надписям (рис. 19.1): itеms=['Армения', 'Грузия', 'Черногория', ui.select(items, 'Казахстан', 'Сербия', 'Узбекистан', 'Босния и Герцеговина') lаЬеl='Выберите страну', with_input=True) Пример списка с возможностью выбора нескольких пунктов (рис. ui.select(items, Выберите lаЬеl='Выберите страну', crpawy .... wrn Выберите страну че~ multiple=True) .... Черногория Армения 19.2): Выберите страну Армения, Черногория Армения Грузия Грузия Казахстан Казахстан Узбекистан Узбекистан Черногория Черногория Сербия Сербия Босtlия и Герцеговина Босния и Герцеговина Рис. 19.1. Раскрывающийся список: слева справа - - в обычном режиме, после фильтрации пунктов по введенной подстроке Рис. 19.2. Раскрывающийся список с возможностью выбора нескольких пунктов (выбраны два пункта)
Урок 19. Разработка веб-приложений, часть 2 Список - 325 поддерживает свойство visiЫe (см. разд. класс Select - ство enaЫed, методы disaЬle () и еnаЫе () ( см . разд. разд. 19.1.2), ♦ value - 17. 3. 5), 17.2.1), свой­ свойство label ( см. а также: свойство, значение пункта, выбранного в списке в настоящий момент (если в списке можно выбрать лишь один пункт), или список значений всех вы­ бранных пунктов (при возможности выбора нескольких пунктов); ♦ метод, задает новые пункты для текущего списка: set _ options () - set_options( < oпиcaниe новых пунктов> [, value=None]) описание новых пунктов указывается в том же формате, а параметр value имеет то же назначение, что и в вызове функции select (). 19.1.7. Поле для ввода числа Поле для ввода целых или вещественных чисел с кнопками, увеличивающими и уменьшающими занесенное число на заданный шаг, создается функцией nшnЬer (): nшnher( [J.abel=None] [, ] [placeholder=None] [, ] [value=' '] [, ] [min=O] [, ] [max=lOO] [, ] [precision=None] [, ] [step=l] [, ] [o n_change=None]) Параметры label, placeholder, value и on_ change аналогичны таковым у функции input () (см. разд. 19.1.2). Остальные параметры: ♦ min - наименьшее доступное для занесения число; ♦ max - наибольшее доступное для занесения число; ♦ количество допустимых цифр после десятичной точки. Если задать precision - None, количество не будет ограничено; ♦ step - шаг, на который увеличивается занесенное число при каждом щелчке на кнопке увеличения или уменьшения. Пример (рис. 19.3): ui.nшnher(label='Bвeдитe цену товара:', value=O.O, min=l . O, precision=2) Введите цену товара: л 12.5 Рис. 19.3. V Поле для ввода числа Поле для ввода числа представляется классом Nшnher . Он поддерживает свойство visiЫe (см. разд. разд. ♦ 17.2.1), 17.3.5), свойства value out_of_limits - свойство enaЫed, методы disaЫe() и laЬel (см . разд. sani tize () - (см . 19.1.2), а также: свойство (только для чтения), True, если введенное число вы­ шло за заданные границы, и ♦ и еnаЫе() False - в противном случае; метод, подгоняет занесенное число под заданные границы.
Часть 326 19.1.8. 111. Практическое Руthоп-программирование Регулятор Регулятор выводится вызовом функции slider (): slider( [min=O] [, ] [max=lOO] [, ] [step=l] [, ] [value=None] [, [on_change=None]) Параметры: ♦ min - наименьшее доступное для установки значение; ♦ max - наибольшее доступное для установки значение; ♦ step - ♦ value - ♦ шаг между устанавливаемыми значениями; изначальное значение, установленное регулятором; on_ change - ссылка на функцию, вызываемую при изменении установленного регулятором значения (обработчик события change). Пример: ui.lаЬеl('Выберите размер изображения:') ui.slider(min=400, max=800, step=lOO, value=600) Регулятор представляется классом Slider, который поддерживает свойство visiЫe (см. разд. 17.2.1), свойство enaЫed, методы disaЫe (), еnаЫе () (см. разд. 17.3.5), а еше свойство value, хранящее значение, которое в настоящий момент установлено регулятором. 19.1.9. Поле для выбора файлов Поле для выбора файлов для выгрузки создается вызовом функции upload () : upload( [multiple=False] [, ] [max_file_size=O] [, ] [max_total_size=O] [, [max_files=O] [, ] [on_begin_upload=None] [, ] [on_upload=None] [, [on_multi_upload=None] [, ] [on_rejected=None] [, ] [label=' ']) Параметров здесь много: ♦ mul tiple - если True, в поле будет можно выбрать произвольное количество файлов, если False - ♦ max _ file _ size - лишь один файл; предельно допустимый размер одного выгружаемого файла в виде целого числа в байтах. Если задать число О, размер не будет ограничен; ♦ max_total_size - предельно допустимый совокупный размер выгружаемых файлов в виде целого числа в байтах. Если задать число О, размер не будет огра­ ничен; ♦ max_files - предельно допустимое количество выгружаемых файлов в виде целого числа. Если задать число О, количество не будет ограничено; ♦ on_begin_upload - ссылка на функцию, вызываемую в начале выгрузки файлов (обработчик события beginuploact);
Урок ♦ 19. Разработка веб-приложений, часть 2 327 ссьmка на функцию, вызываемую после выгрузки очередного фай­ on_ upload - ла (обработчик события uploact); ♦ on_ rnul ti _ upload - ссылка на функцию, вызываемую после выгрузки всех вы­ бранных файлов (обработчик события rnultiuploact); ♦ on_ rej ected - ссылка на функцию, вызываемую, если какие-либо из выбранных файлов не были выгружены, поскольку не удовлетворяют заданным условиям (например, их количество или совокупный размер превышают заданные вели­ чины); ♦ label - текст надписи, выводимой над полем. В функцию, задаваемую в параметре on_ upload, единственным параметром переда­ ется объект класса UploadEventArgurnents из модуля nicegui. events, хранящий све­ дения о выгруженном файле. Этот объект имеет три атрибута: ♦ content - содержимое выгруженного файла в виде потока класса BinaryIO из модуля io стандартной библиотеки; ♦ narne - имя файла; ♦ type - МIМЕ-тип файла в виде строки. В функцию, задаваемую в параметре on_rnulti_upload функции upload, передается объект класса Mul tiUploadEventArgurnents из модуля nicegui. events, хранящий све­ дения о выгруженных файлах. Он поддерживает также три атрибута: ♦ contents - список потоков класса BinaryIO из модуля io, хранящих содержимое выгруженных файлов; ♦ narnes - список имен файлов; ♦ types - список МIМЕ-типов файлов. Чтобы запустить выгрузку выбранных файлов из поля, следует вызвать у него со­ программу-метод run _ rnethod () , передав ему в параметре строку 'upload'. Очистить поле от выбранных файлов можно, вызвав у него метод reset () . Пример выгрузки и сохранения на локальном диске одного файла (рис. 19.4): # Обработчик момента окончания выгрузки def on_file_uploaded(evt): # Получаем содержимое выгруженного файла file content = evt.content # Создаем на локальном диске целевой файл, одноименный выгруженному f = open(f'c:/webapps/rnywebapp/uploads/(evt.narnel', rnode='wb') # Пока есть что считывать из выгруженного файла # (пока метод read() потока не вернет None), # считываем файл по частям ... while (с:= file_content.read()): # ... и записываем в целевой файл на локальном диске f. write (с) # После записи не забываем закрыть файл f. close ()
328 Часть 111. Практическое Руthоп-программирование # По нажатию кнопки Быгрузить фай:п;.~ запускаем # после чего очищаем поле async def upload_file(): await uploader.run_rnethod('upload') uploader.reset() выгрузку файлов, uploader = ui.upload(on_upload=on_file uploaded) файлы', on_click=upload_file) ui.Ьuttоn('Выгрузить 11 0 ::: 380.SKB / О 00% .= 4). 193.SKB/000% Раэра6оnса ве6-nриnожений 187. ОКВ Работа с базами данных.dос 193 . SКВ Рис. / 0.00% 19.4. Работа с базами данных.dос х Рис. 19.5. х х 193 . SКB/O.O0'lr. Поле для выбора одного файла (выбран документ 1.doc / 0.00'lr. Поле для выбора произвольного количества файлов Microsoft Word) (выбраны два документа Microsoft Word) Пример выгрузки и сохранения произвольного количества файлов (рис. 19.5): def on_files_uploaded(evt): for file index in range(len(evt.contents)): file_content = evt.contents[file_index] f = open(f'c:/webapps/rnywebapp/uploads/{evt.narnes[file index] )', rnode= ' wb ' ) while (с := file_content.read()): f.write(c) f .close () async def upload_files(): await uploader.run_rnethod('upload') uploader.reset() uploader = ui.upload(rnultiple=True, on_rnulti_upload=on_files_uploaded) файлы', on_click=upload_files) ui.Ьuttоn('Выгрузить Поле для выбора файлов представляется классом Upload. Он поддерживает свой­ ство visiЫe (см. разд. ( см. разд. 17.2.1), свойство еnаЫес:1, методы disaЫe () и еnаЫе () 17.3.5), свойство label (см. разд. 19.1.2), а также уже упоминавшиеся ранее сопрограмму-метод 19.2. run_rnethod () и метод Хранилища данных Библиотека NiceGUI reset () . NiceGUI позволяет сохранять произвольные данные в одном из предос­ тавляемых ею хранилищ. Они имеют функциональность обычных словарей и доступны из следующих переменных, созданных в модуле Python nicegui. арр. storage:
Урок ♦ 19. Разработка веб-приложений, часть 329 2 хранит данные, относящиеся к текущему пользователю, на сервере (ком­ user - пьютере, на котором запущено приложение). Доступно только в функциях, реа­ лизующих страницы приложения. Требует указания секретного ключа в вызове функции run() (см.разд. 17.1.6). Применяется, в частности, для хранения признака того, выполнил ли пользова­ тель вход в приложение. Пример: from nicegui import арр app.user['is_user_logged_in'] = True if app.user['is_user_logged_in']: ♦ browser - хранит данные, относящиеся к текущему веб-обозревателю, в самом веб-обозревателе. Доступно только в функциях, реализующих страницы прило­ жения. Требует указания секретного ключа в вызове функции run ( J. Может применяться, например, для хранения настроек веб-приложения; ♦ general - хранит данные, относящиеся ко всем пользователям, на сервере. Дос­ тупно везде. 19.3. Хеширование паролей В разд. 18. 9. 1 говорилось, что в модели пользователя следует хранить не сами пользовательские пароли, а их хеши - ради безопасности. Для вычисления хеша на основе заданного пароля с помощью алгоритма с указан­ ным названием служит функция pbkdf2 _ hmac () из модуля hashlib стандартной биб­ лиотеки: рЬkdf2_hmас(<название алгоритма>, <хешируемый пароль>, <соль>, <количество итераций>) Название алгоритма задается в виде строки. Автор использовал алгоритм 'sha512' на сегодняшний момент наиболее устойчивый ко взлому. Хешируемый пароль следует задать в виде последовательности байтов типа bytes. Преобразовать строку в последовательность байтов можно, вызвав конструктор класса bytes (см.разд. соль - 4. 4) и указав в нем кодировку 'latinl' ( основная латиница). это секретный ключ, участвующий в вычислении хеша. Она должна пред­ ставляться последовательностью байтов типа bytes и иметь размер не менее количество 16. итераций напрямую влияет на устойчивость вычисленного хеша ко взлому и задается в виде целого числа. Автор далее в примере использовал значе­ ние 100 ООО, однако при разработке коммерческих приложений его следует увели­ чить хотя бы до 500 ООО.
Часть 330 111. Практическое Руthоп-программирование Функция возвращает хеш в виде последовательности байтов типа bytes. При ис­ пользовании алгоритма длина хеша 'sha512' составляет вании 64 байта. При преобразо­ его в шестнадцатеричное строковое представление длина получившейся строки составит 128 символов (именно поэтому в разд. 18.9.1 мы задали такую длину у поля пароля). 19.4. Веб-приложение фотогалереи, версия Напишем версию 3.0 3.0 веб-приложения фотогалереи. Сделаем так, чтобы зарегист­ рированные пользователи могли выполнять вход в приложение, публиковать от своего имени новые изображения и удалять опубликованные ими ранее. Также добавим возможность регистрации новых пользователей. Код новой версии приложения будет большим, поэтому мы станем рассматривать его по частям. 19.4.1. Верхнее меню и секретный ключ В верхней части каждой страницы мы поместим навигационное меню с гиперссыл­ ками Главная, Вход, Профиль, Добавить изображение, Выход и Регистрация, ведущие на соответствующие страницы. Гиперссылки Профиль, Добавить изо­ бражение и Выход будут присутствовать на странице только после выполнения входа, а гиперссылки Вход и Регистрация - напротив, если вход не бьm выпол­ нен. Страница входа будет находиться по шаблонному пути /login, страница пользова­ /profile, страница добавления изображения - по пу­ выхода - /logout, а страница регистрации - /register. тельского профиля ти /add, страница - по пути Признак того, был ли выполнен вход, будем хранить в хранилище user (см. разд. 19.2) в элементе с ключом current_user. Он будет представлять собой ключ (значение ключевого поля) текущего пользователя (выполнившего вход). Этот ключ понадо­ бится нам позже, когда мы начнем реализовывать добавление изображений. Код верхнего меню оформим в виде функции top_ menu (), которую сохраним во вновь созданном модуле modules\topmenu.py (листинг from nicegui import ui, 19.1). арр def top_menu(): with ui.row() .classes('full-width bg-grey-4 q-pa-md'): ui. link ('Главная', '/') if 'current_user' in app.storage.user: ui.link('Пpoфиль', '/profile') ui. link ('Добавить изображение', '/ add') ui. link ('Выход', '/logout') # 1 # 2
Урок 19. Разработка веб-приложений, часть 2 331 else: # 3 '/login') ui.link('Регистрация', '/register') ui.link('Bxoд', Гиперссьmки мы помещаем в блок-строку (см. разд. ге 19.1). класс 17.3.1 - поз. 1 в листин­ Это позволит нам без проблем расположить их по горизонтали. Стилевой q-pa-rnd задает среднего размера внутренние просветы со всех сторон - так мы отделим гиперссьmки от границ блока-строки. Если в хранилище user присутствует элемент с ключом current_user (т. е. если пользователь выполнил вход), мы выводим гиперссылки Профиль, Добавить изо­ бражение и Выход (поз. ция (поз. 2), в противном случае - гиперссылки Вход и Регистра­ 3). Хранилище user для работы требует указания секретного ключа в вызове функции run () . Кроме того, нам понадобится соль для хеширования паролей. Впрочем, в ка­ честве соли мы можем использовать тот же секретный ключ, преобразовав его в последовательность байтов. Поместим код, задающий секретный ключ и соль, в модуль modules\secret.py. Сек­ ретный ключ присвоим переменной тинг secret, а соль - переменной sal t (лис­ 19.2). •~5#A:#~~=~•1&ilii&iЫd!'• &галереи,. Щ....·э~о,·.мор.уль moaш5\fkr_e_t.py_. _ ___. secret = '1234567890' salt = bytes(secret, 'latinl') В учебном приложении (как у нас) можно указать простой секретный ключ. В ком­ мерческих приложениях его необходимо сделать более сложным. 19.4.2. Страницы входа и выхода На странице входа пользователь введет свои регистрационное имя и пароль, нажмет кнопку Вход и в зависимости от правильности ввода либо попадет на стра­ ницу своего пользовательского профиля (которую мы напишем потом), либо полу­ чит сообщение об ошибке. Страница выхода будет просто сообщать об успешном выходе из приложения. Код страниц входа и выхода вынесем в модуль modules\user.py (листинг frorn frorn frorn frorn frorn frorn nicegui irnport ui, арр hashlib irnport pbkdf2_hrnac tortoise.exceptions irnport DoesNotExist rnodules.toprnenu irnport top_rnenu rnodules.rnodels irnport User rnodules.secret irnport salt 19.3).
Часть 332 111. Практическое Руthоп-программирование Фотогалерея' ) @ui.page('/login', title='Bxoд def login(): async def logging_in(): try: user = await User.get(name=username.value) bpassword = bytes(password.value, 'latinl') hash = pbkdf2_hrnac('sha512', bpassword, salt, 100000) if hash.hex() == user.password: арр. storage. user [ 'current_ user'] = user. pk ui.navigate.to('/profile') else: error_message.text = 'Неправильный пароль' except DoesNotExist: error_message.text = 'Пользователя с таким именем нет' top_menu() with ui. element ( 'main') \ .classes('full-width text-center q-col-gutter-md'): ui. label ( 'Фотогалерея') . classes ( 'text-hl' ) ui. label ('Вход') . classes ( 'text-h2') username = ui. input ('Имя пользователя: ') password = ui.input('Пapoль: ', password=True) ui.button('Boйти', on_click=logging_in) error_message = ui.label('') @ui. page ( '/ logout', ti tle= 'Выход : : Фото галерея') def logout(): del app.storage.user['current_user'] top_menu() wi th ui. element ( 'main') \ .classes('full-width text-center q-col-gutter-md'): ui.lаЬеl('Фотогалерея') .classes('text-hl') ui.label('Bыxoд') .classes('text-h2') ui. label ('Выход успешно вьnюлнен') Страницу входа выведет функция login () (поз. 1в листинге 19.3). # # 1 6 # 7 # 8 # 9 # 10 # 11 # 12 # 13 # 14 # 2 # 3 # # 5 4 # 15 # 16 # 17 В верхней части этой страницы отображаем навигационное меню, вызвав функцию top_ menu () из модуля modules\topmenu.py (поз. 2). Далее помещаем поля для ввода имени пользова­ теля и пароля (поз. 3), кнопку Войти, назначив ей еще не объявленную сопрограм­ му logging_in() в качестве обработчика щелчка (поз. пись для вывода сообщений об ошибках (поз. 4) и изначально пустую над­ 5). Объявим вложенную сопрограмму logging_ in (), которая будет выполнять вход (поз. 6). В ее теле ищем пользователя с заданным в поле ввода именем (поз. 7), пре­ образуем заданный в другом поле ввода пароль в последовательность байтов (поз. (поз. 8), 9), вычисляем хеш этого пароля, использовав соль из модуля modules\secret.py сравниваем его шестнадцатеричное представление с хранящимся в данных
Урок 19. Разработка веб-припожений, часть найденного пользователя (поз. элемент с ключом current _ user, 10) 333 2 и, если они равны, создаем в хранилище user присваиваем ему ключ текущего пользователя, тем 11), и производим перенаправление на 12). Если шестнадцатеричное представ­ самым отмечая, что вход был выполнен (поз. страницу пользовательского профиля (поз. ление хеша введенного пароля не совпадает с хранящимся в данных пользователя, выводим соответствующее сообщение в созданной ранее надписи (поз. 13). Если пользователь с заданным именем не был найден, также выводим сообщение об этом (поз . 14). Страницу выхода реализуем функцией logout ( J (поз. 15). В ее теле удаляем из хра­ нилища user созданный ранее элемент current user (поз. с уведомлением об успешном выходе (поз. 17). Также 16) и выводим надпись не забываем вывести на этой странице верхнее меню (как и на всех последующих страницах). 19.4.3. Страница пользовательского профиля На странице пользовательского профиля выведем перечень изображений, опубли­ кованных текущим (выполнившим в настоящий момент вход) пользователем. Каж­ дая позиция этого перечня включит название изображения, его миниатюру и rипер­ ссылку Удалить, ведущую на страницу удаления изображения. Страница удаления изображения будет связана с шаблонным путем вида: /dеlеtе/<ключ записи с удш~яемым изо6ра:жением> Код страницы профиля (листинг ный в разд. Лис-rинг 19.4) добавим в модуль modules\user.py, написан­ 19.4.2. 19.4. Веб-nриnожение фотогалереи, версия 3.0, модуль modules\user.ру (исправления) fran IIIOdules . пюdels import User, Iшage @ui. page ( '/ logout', ti tle= 'Выход : : def logout(): @ui.page('/profile', async def profile(): titlе='Профкпь Фотогалерея') .. Фото:rалерея') # 1 wi th ui. element ( 'main' ) \ .classes{'full-width text-center q-ool-gutter-ш::I.'): ui. laЬel ( 'Фотогаперея' ) . classes ( 'text-hl' ) ui. laЬel ( 'Профи.ль • ) . classes ( 'text-h2 ' ) # with ui.grid(rows='auto', oolumns='lfr lfr 100рх')\ . classes('q-mt-ш::I. items-center'): # async for i.ma.ge in \ Image.filter(user=app.storage.user['current_user']): ui. laЬel (image. title) . classes ( 'text-hб') 2 top_menu() з
Часть 334 111. Практическое Руthоп-программирование ui.image(f'/uploaded/{image.filename}')\ . classes ( 'w-full ' ) ui. link ( 'Удалить' , f' / delete/ { image. pk} ' ) Страницу профиля будет выводить сопрограмма profile () (поз. l # 4 # 5 в листинге 19.4). Это должна быть именно сопрограмма, поскольку в ее теле мы будем обращаться к конкурентным механизмам библиотеки Tortoise ОRМ. Для вывода перечня изображений используем блок-сетку (см. разд. Все строки сетки получат одинаковую высоту - 17.3.4 - поз. 2). такую, чтобы вместить содержи­ мое. Первые два столбца сетки, выводящие соответственно название изображения и само изображение, будут иметь одинаковую ширину, равную одной свободной доле, а последний столбец с гиперссылкой Удалить - 100 пикселов. Стилевой класс q-mt-md, указанный у блока-сетки, задаст внешний просвет сверху среднего размера ( он немного отделит блок от заголовка), а стилевой класс i tem-center - вертикальное выравнивание потомков по центру. Мы отфильтровываем изображения по ключу опубликовавшего их текущего поль­ зователя, который хранится в элементе current_user хранилища user (поз. 3), и вы­ водим в строках блока-сетки название, миниатюру каждого изображения и гипер­ ссылку Удалить. Стилевой класс w-full, указанный у миниатюры, задаст для нее ширину, равную ширине родителя (поз. 4). В выводимой гиперссылке формируем интернет-путь согласно приведенному ранее шаблонному пути (поз. 5). Чтобы вновь написанные страницы работали, необходимо вставить в код стартово­ го модуля арр.ру выражение импорта модуля modules\user.py, в котором объявлены выводящие их функции. Кроме того, следует добавить в вызов функции run () па­ раметр storage_ secret с секретным ключом. Внесем в модуль арр.ру необходимые исправления: from modules.secret import secret app.add_media_files('/uploaded', join(dirname( file ), 'uploaded')) import modules.user @ui.page('/', titlе='Главная async def main(): Фото галерея' ) ui.run(storage_secret=secret) Теперь можно проверить веб-приложение в работе. Запустим его, дождемся вывода главной страницы и щелкнем на расположенной в верхнем меню гиперссылке Вход. На странице входа (рис. 19.6) выполним вход от имени одного из имеющих­ ся в базе данных пользователей: userl с паролем 11111111 или user2 с паро­ лем 22222222. Проверим, правильно ли выводится страница пользовательского про­ филя (рис. 19.7), и выполним выход, щелкнув на гиперссьmке Выход в верхнем меню.
Урок 19. Разработка веб-приложений, часть 2 [дallJ!aJ! вход 335 !'~ Фото галерея Вход ИмR пользователя . user2 ........ Пароnь: Рис. 19.6. Веб-приложение фотогалереи, страница входа Фото галерея Профиль Красно-желтый цветок .l'дi\JJIШ> Шиповник белый Рис. 19.4.4. 19.7. Веб-приложение фотогалереи, страница пользовательского профиля Страницы добавления и удаления изображений Код, выводящий страницы для добавления и удаления изображений (листинг вынесем в отдельный модуль modules\images.py. 19.5),
Часть 336 from from from from from from from 111. Практическое Руthоп-программирование nicegui import ui, арр os.path import dirname, join, splitext time import time tortoise.exceptions import DoesNotExist os import remove modules.models import User, Image modules.topmenu import top_menu upload_path = join(dirname(dirname( file )), 'uploaded') @ui.page('/add', titlе='Добавление изображения:: def add(): async def upload_image(): awai t image. run_ method ( 'upload' ) # 1 # 2 # # 3 4 # # 5 6 # 7 Фотогалерея') async def add_image(evt): file content = evt.content filename = f'{time() ){splitext(evt.name) [1] }' filepath = join(upload_path, filename) with open(filepath, mode='wb') as f: while (с:= file_content.read()): f.write(c) си= await User.get(pk=app.storage.user['current_user']) await Image.create(title=title.value, desc=desc.value, filename=filename, user=cu) ui. navigate. to ( '/profile') top_menu() with ui.element('main')\ .classes('full-width text-center q-col-gutter-md'): ui. label ( "Фотогалерея') .classes ( 'text-hl') ui.lаЬеl('Добавление изображения') .classes('text-h2') title = ui. input ('Название:') desc = ui. textarea ( 'Описание: ' ) image = ui.upload(on_upload=add_image, lаЬеl='Файл с изображением') ui. but ton ( 'Добавить ', on_ click=upload_ image) @ui .page ( '/delete/ {pk} ') async def delete(pk: int): async def delete_image(): try: image = await Image.get(pk=pk) filepath = join(upload_path, image.filename) remove(filepath) await image.delete() # 8 # 11 # 12 # 13 # 14 # 15
Урок 19. Разработка веб-приложений, часть 337 2 # 16 except DoesNotExist: pass ui.navigate.to('/profile') # 17 top_menu() with ui.element ( 'main') \ .classes('full-width text-center q-col-gutter-md'): ui.lаЬеl('Фотогалерея') .classes('text-hl') ui.lаЬеl('Удаление изображения') .classes('text-h2') try: image = await Image.get(pk=pk) ui.label(image.title) .classes('text-hЗ q-mt-lg') ui.image(f'/uploaded/{image.filename}')\ .classes('w-5/6 q-mt-sm') ui. label (' ') ui.button('Yдaлить', on_click=delete_image) except DoesNotExist: ui.lаЬеl('Выбранное изображение # 10 uploaded, в которой хранятся 1 в листинге 19.5). Страница добавления изображения выводится сопрограммой add () (поз. аналогичен коду примера из разд. 9 отсутствует.') В коде модуля мы сразу же вычисляем путь к папке все выгруженные изображения (поз. # 19.1. 9, 2). Ее код поэтому мы рассмотрим лишь его ключе­ вую часть. Функция time () из модуля time возвращает количество секунд, прошедших с полу­ ночи 1 января 1970 года, в виде вещественного числа. Мы используем это значение в качестве имени целевого файла, добавив к нему расширение исходного файла 3). Сгенерированное таким образом имя файла мы добавляем uploaded и получаем полный путь к целевому файлу (поз. 4). с изображением (поз. к пути к папке После сохранения выгруженного изображения в файле ищем запись текущего поль­ зователя по ключу, хранящемуся в элементе current_user хранилища user (поз. 5). Далее создаем в модели Image запись, представляющую только что выгруженное изображение (поз. 6). В полях title и desc новой записи сохраняем название и опи­ сание, занесенные пользователем в соответствующие поле ввода и область редак­ тирования. В поле а в поле user - filename помещаем сгенерированное ранее имя целевого файла, ссьmку на запись текущего пользователя. Сохранив новое изобра­ жение, выполняем перенаправление на страницу профиля сразу увидел результат (поз. Код сопрограммы (поз. 8), - чтобы пользователь 7). ctelete (), выводящий страницу для удаления изображения также не требует особых пояснений. Поэтому ограничимся наиболее при­ мечательными фрагментами. В теле этой сопрограммы ищем изображение по полученному в URL-параметре ключу и выводим на экран его название и миниатюру (поз. тель знал, что удаляет. Под миниатюрой помещаем 9) - чтобы пользова­ пустую надпись (поз. 1О)
Часть 338 111. Практическое Руthоп-программирование и кнопку Удалить. Если не вставить пустую надпись, библиотека выведет миниа­ тюру и кнопку по горизонтали, в строкуl, что нам совсем не нужно. Удаление изображения выполняет сопрограмма delete_image () (поз. 11), вызывае­ мая по щелчку на кнопке Удалить. В ее теле мы ищем удаляемое изображение (поз. 12), вычисляем путь к файлу, в котором оно хранится (поз. файл вызовом функции remove () из модуля os (поз. этом изображении (поз. (поз. 15). Вставим 15). 14) 13), удаляем этот и удаляем саму запись об Если изображение не было найдено, ничего не делаем Напоследок производим перенаправление на страницу профиля (поз. в код стартового модуля арр.ру выражение импорта модуля 17). modules\ images.py: import modules.user import modules.images Запустим приложение, выполним вход от какого-либо пользователя, попробуем добавить пару изображений и удалить одно из них. После чего выйдем из приложе­ ния, выполним вход теперь уже от имени другого пользователя и добавим еще одно изображение. Перейдем на главную страницу и проверим, выводятся ли на ней два добавленных изображения. 19.5. Что еще нужно знать о библиотеке часть NiceGUI, 2 предоставляет огромное количество разнообразных элементов страниц: NiceGUI поля для ввода значений даты, времени, цвета, мощный онлайновый редактор про­ граммного кода, таблицу для вывода данных, иерархический список, средства для отображения разнообразных графиков и диаграмм, в том числе трехмерных, меню, диалоговую панель, блокнот с вкладками, всплывающее уведомление и др. Все эти элементы страницы описаны в документации по библиотеке. 19.6. Самостоятельное упражнение Реализуйте в веб-приложении фотогалереи регистрацию новых пользователей. Пе­ ред созданием нового пользователя проверяйте, не существует ли уже пользователь с таким именем в базе данных. После успешной регистрации пользователя произ­ водите перенаправление на страницу входа. Поместите код, выводящий страницу регистрации, в модуль modules\user.py. Заодно вынесите код, выводящий главную страницу и страницу со сведениями о выбранном изображении, в отдельный модуль 1 Вероятно, это ошибка в коде NiceGUI. modules\main.py.
Урок 20 Разработка графических приложений На Python также можно писать приложения с графическим интерфейсом, работаю­ щие на разных платформах: Windows, Linux и macOS. Библиотека Toga: разработка графических приложений 20.1. Toga - вероятно, наиболее простая и удобная в применении дополнительная биб­ лиотека для написания графических приложений. Установить версию 0.5.* этой библиотеки, о которой идет речь в книге, можно с помощью команды: pip install "toga==0.5" На заметку Полное описание библиотеки Toga доступно по интернет-адресу: https://toga.readthedocs.io/. 20.2. Основы библиотеки 20.2.1. Toga Toga Окна и контейнеры чем-то похожа на NiceGUI (см. уроки 17 и 19) - каждое окно, выводимое приложением, реализуется отдельной функцией. В теле этой функции создается интерфейс окна - все элементы управления, которые должны в нем присутство­ вать. Имя функции значения не имеет. Напишем простое учебное приложение, переводящее введенную величину размера из дюймов в сантиметры (листинг принципы работы с библиотекой 20 .1 ). Toga. И на его примере рассмотрим основные import toga def startup(app): main_box = toga.Box() inches label = toga.Label ('дюймы:') inches_input = toga.Textinput(value=O) - # 1 # 2 # # 3 4
Часть 340 button 111. Практическое Руthоп-программирование # 5 cms label = toga. Label ('Сантиметры:') cms_output = toga.Textinput(value=O, readonly=True) # # 6 7 main_box.add(inches label, inches_input, button, cms label, cms_output) # 8 return main # 9 арр tоgа.Вuttоn('Преобразовать') Ьох tоgа.Арр('Преобразователь # 10 величин', 'python-22-lessons.convertor', startup=startup) арр. main_ loop () # 11 Функцию, которая создаст интерфейс главного окна приложения и задаст его на­ чальные настройки, назовем Кон_тейнер 11 startup () (поз. 1 в листинге 20.1 ). элемент окна, выстраивающий помещенные в него элементы и задаю­ щии их размеры согласно реализованным в нем правилам. Сначала следует создать контейнер, который вместит в себя всё содержимое окна. Мы выбрали контейнер-стопку, выстраивающий помещенные в него элементы по горизонтали или вертикали (в зависимости от заданных настроек). Он представля­ ется классом вох из модуля toga. Мы создаем его объект и присваиваем его пере­ менной далее он нам понадобится (поз. - Затем создаем надпись Дюймы (поз. (поз. 4). ввода - 2). 3) и поле для ввода величины в дюймах Обычная надпись представляется классом Label из модуля toga, а поле классом в параметре value Textinput из того же модуля. В конструкторе класса тextinput указываем изначальное значение для поля. После этого создаем кнопку Преобразовать (поз. сом Button из модуля toga, 5). и его конструктору передается текст надписи для кнопки. Далее формируем надпись Сантиметры (поз. 6) и поле ввода, в котором будет вы­ водиться рассчитанная величина в сантиметрах (поз. тупным только для чтения, дав параметру значение Кнопка представляется клас­ readonly 7). Поле ввода мы делаем дос­ конструктора класса Textinput True. Чтобы все созданные надписи, поля ввода и кнопка появились в окне, их следует добавить в подготовленный ранее контейнер. Это выполняется вызовом у контей­ нера метода add () с передачей ему всех добавляемых элементов (поз. 8). Останется лишь вернуть контейнер из функции startup () в качестве результата таково требование - Toga (поз. 9). Теперь следует создать объект класса Арр из модуля toga, представляющий само приложение. Конструктору класса мы передаем название приложения, уникальный идентификатор приложения и в параметре startup ссылку на функцию startup (), формирующую главное окно (поз. 1О).
Урок 20. Разработка графических приложений 341 И наконец, запускаем приложение, инициировав в нем цикл обработки событий вызовом метода main loop () у объекта приложения (поз. 11 ). Запустим приложение и посмотрим на появившееся окно (рис. W Преобразователь величин File х □ Help Дюймы: о ------ Рис. 20.1). 20.1. Преобразовать Сантиметры : о ------ Окно графического приложения преобразователя величин По умолчанию главное окно приложения получает произвольные местоположение и размеры. Оно изначально содержит главное меню с пунктами шает работу приложения) и Help I About File I Exit (завер­ (выводит окно с названием приложения, заданным при создании его объекта). Размеры окна можно менять, окно можно раз­ вертывать на весь экран и свертывать в панель задач (в разд. 20. 7.1 мы узнаем, как задать другие настройки главного окна). На текущий момент при нажатии кнопки Преобразовать приложение ничего не делает. Нужно это исправить. 20.2.2. Обработка событий Когда в каком-либо элементе окна что-либо происходит, возникает соответствую­ щее событие. Так, по щелчку на кнопке в ней возникает событие press. В этом биб­ лиотека Toga также подобна библиотеке NiceGUI. Обработчики событий назначаются при создании элементов окна - присваиванием ссылок на них особым параметром конструктора соответствующего класса. И что­ бы назначить обработчик событию press кнопки, следует присвоить ссылку на него параметру on_press конструктора класса Button. Функции-обработчику передается единственный параметр - ссылка на элемент окна, в котором возникло событие. Если обработчиком является метод, то ссылка на объект передается ему вторым параметром (первым параметром, как мы пом­ ним, любой метод принимает ссылку на текущий объект). Заставим наше приложение выполнять полезную работу величину из дюймов в сантиметры (листинг Листинr 20.2. - переводить введенную 20.2). Приложение преобразователя величин, полностью работающее (исправnения) def startup(app): def convert (Ьutton) : cms output.value rnain Ьох = toga. Вох () = float(inches_input.value) * 2.54
Часть 342 111. Практическое Руthоп-программирование button = toga. Button ( 'Преобразовать' , onyress=convert) return rnain Ьох Мы объявили вложенную функцию convert () и указали ссылку на нее в параметре on_press конструктора класса вutton, тем самым назначив ее обработчиком собы­ тия щелчка на этой кнопке. Значение, занесенное в поле ввода, получаем из свойства value объекта кнопки. Поскольку это значение представляется в виде строки, перед вычислениями преоб­ разуем его в вещественное число. Запустим приложение и проверим, как оно работает. Всем хорошо наше первое графическое приложение! Вот только вид у него уж слишком непрезентабельный ... 20.2.3. Оформление приложения. Стили Каждый элемент окна содержит встроенный стиль, задающий его оформление и аналогичный стилю CSS 1. Изменить внешний вид элемента можно, присвоив свой­ ствам этого стиля соответствующие значения. Стиль этот хранится в свойстве style объекта элемента окна. Давайте выстроим элементы в окне нашего приложения по вертикали, зададим просветы между границами окна и его содержимым равными отдельными элементами - 1О 1О пикселам, а между пикселам. Для этого достаточно внести в код при­ ложения совсем немного правок (листинг 20.3). - def startup(app): rnain_box = toga.Box() = = 20 = 10 main_Ьox.style.di.rection 'colшnn' main_Ьox.style.margin main_Ьox.style.gap Мы задали выстраивание элементов по вертикали, присвоив свойству direction встроенного стиля контейнера-стопки значение 'colurnn'. Просвет между граница­ ми контейнера и его родителя (в нашем случае окна) в пикселах задается свойством rnargin стиля, а просвет между элементами в контейнере 1 Вот еще одна причина изучить этот язык. - свойством gap.
Урок 20. Разработка графических приложений .., Преобразователь величин 343 о х Help File Дюймы: Преобразовать Сантиметры : 2.54 Рис. 20.2. Окно приложения после оформления Запустим приложение и посмотрим, как оно сейчас выглядит (рис. 20.2). Ну, совсем другое дело! 20.2.4. Запуск и останов приложения Чтобы запустить графическое приложение, написанное с применением Toga, доста­ точно создать его объект и активизировать цикл обработки событий. Приложение представляется классом Арр из модуля t oga. Его конструктор вызыва­ ется в следующем формате: Арр (< выв одимо е название приложения> , < идентификат ор приложения>[ , app_ narne=None] [, i con=None] [ , a u tho r=None] [, version=None ] [ , home _page=None ] [, des c ription=None] [, startup=None ] [, on_running=None ] [, on_ex i t=None ]) Выв одимо е название и идентификатор должны быть указаны в виде строк. Идентифика ­ тор должен быть уникальным и представлять собой набор слов, разделенных точ­ ками (см . пример в разд. 20.2.1). Остальные параметры: ♦ app_narne - внутреннее имя приложения . Если не указано, будет взято последнее слово заданного идентифика т ора ; ♦ i con - путь к файлу значка приложения в виде строки. Значок может храниться в файле формата PNG, ICO или ВМР. Если не указан, будет использоваться зна­ чок, поставляемый в составе ♦ autho r - Toga; имя автора приложения. Выводится в стандартном окне сведений о приложении; ♦ version - номер версии приложения в виде строки. Выводится в стандартном окне сведений о приложении; ♦ h ome_page - интернет-адрес «домашнего» сайта приложения. Если указан, в ав­ томатически создаваемом меню домашнюю страницу); Help появится пункт Visit homepage (Посетить
344 ♦ Часть description - 111. Практическое Руthоп-программирование краткое описание приложения. Выводится в стандартном окне сведений о приложении; ♦ startup - ссьmка на функцию, создающую интерфейс главного окна приложе­ ния. Это должна быть именно функция - не сопрограмма. Если не указана, главное окно будет пустым; ♦ on_running - ссылка на функцию, которая будет вызвана после создания при­ ложения, вывода его главного окна и инициирования цикла обработки событий (обработчик события «запуск приложения»). Эта функция должна получать с параметром объект приложения; ♦ on_ exi t - ссылка на функцию, которая будет вызвана при завершении прило­ жения (обработчик события «завершение приложения»). Функция должна полу­ чать с единственным параметром объект приложения и возвращать тrue, чтобы позволить окну закрыться, или False для предотвращения его закрытия. Для инициирования цикла обработки событий в приложении, что вызовет его запуск, достаточно вызвать метод main_ loop () у объекта приложения. Пример: from os.path import dirname, join icon_path = join(dirname( file ), 'icon.png') = tоgа.Арр('Преобразователь величин', 'python-22-lessons.convertor', app_name='inches-to-cms-convertor', icon=icon_path, аuthоr='Читатель', version='l.0.0', арр dеsсriрtiоn='Преобразователь из дюймов в сантиметры', startup=startup) app.main_loop() Приложение автоматически завершится после закрытия последнего созданного им окна. Если требуется завершить приложение принудительно, следует вызвать метод exi t () у объекта приложения: def exit_buttom_pressed(button): app.exit () 20.3. Простейшие элементы окон Все классы, описываемые далее, объявлены в модуле toga, если не указано иное. 20.3.1. Свойства и методы, поддерживаемые всеми элементами окон ♦ enaЫed если свойство, если True, текущий элемент доступен для взаимодействия, False - недоступен;
Урок ♦ 20. 345 Разработка графических приложений свойство, хранит объект встроенного стиля, задающего оформление style - текущего элемента; ♦ метод, перемещает фокус ввода на текущий элемент focus () - (если этот эле­ мент может получать фокус ввода). 20.3.2. Надпись Обычная текстовая надпись представляется классом Label. Вот формат вызова кон­ структора этого класса: LаЬеl(<текст надписи>) Класс Label поддерживает свойство text, хранящее текст надписи. 20.3.3. Поле ввода Обычное поле ввода представляет класс Textinput. Его конструктор вызывается в формате: Textinput ( [value=None] [, ] [ readonly=False] [, ] [placeholder=None] [, [on_change=None] [, ] [on_gain_focus=None] [, ] [on_lose focus=None]) Параметры: ♦ значение, которое должно присутствовать в поле ввода изначально. value - Если задать None, поле будет пустым; ♦ readonly - если True поле будет доступно только для чтения, если False - для чтения и записи; ♦ placeholder - ♦ on_ change - строка, которая будет выводиться в пустом поле ввода; ссылка на обработчик события «изменение занесенного в поле зна- чения>>; ♦ on_gain _ focus - ссылка на обработчик события «получение фокуса ввода»; ♦ on_lose_focus - ссылка на обработчик события «потеря фокуса ввода». Класс тextinput поддерживает свойство value, хранящее занесенное в поле значе­ ние в виде строки. 20.3.4. Область редактирования Область редактирования - это класс Multilineтextinput, конструктор которого вызывается в формате: MultilineTextinput([value=None] [, ] [readonly=False] [, ] [placeholder=None] [, ] [on_change=None]) Параметры аналогичны таковым у конструктора класса Textinput (см.разд. 20.3.3).
Часть 346 111. Практическое Руthоп-программирование Класс MultilineTextinput поддерживает свойство value (см. разд. 20.3.3) и два ме­ тода: ♦ scroll _ to _ top () - прокручивает текущую область редактирования до самого верха; ♦ scroll _ to _ bot torn () - прокручивает текущую область редактирования до самого низа. 20.3.5. Флажок Флажок представляется классом Swi tch. Формат вызова его конструктора: Swi tch ( <текст надписи> [, on_ change=None) [, value=False) [, enaЬled=True] ) текст надписи, которая будет выводиться справа от флажка, задается в виде строки. Остальные параметры: ♦ on change - ♦ value - ♦ enaЫed - ссылка на обработчик события «изменение состояния флажка»; изначальное состояние флажка: True (установлен) или False (сброшен); если True, флажок будет доступен для взаимодействия, если False - недоступен. Пример: toga.Switch('Bывoдить адрес в списке?', value=True) Класс switch поддерживает следующие свойства и методы: ♦ text - свойство, текст надписи; ♦ value - свойство, текущее состояние флажка: True (установлен) или False (сброшен); ♦ toggle () - 20.3.6. метод, меняет текущее состояние флажка на противоположное. Кнопка Обычная кнопка представляется классом Button, конструктор которого вызывается в формате: Button( [text=None) [, ) [icon=None] [, ] [on_press=None] [, ) [enaЫed=True]) Параметры: ♦ text - текст надписи, выводящейся на кнопке, в виде строки. Если задать None, кнопка будет выведена без надписи; ♦ icon - путь к файлу значка, который отображается на кнопке вместо надписи, в виде строки. Значок может храниться в файле формата PNG, ICO Если не указан, значок выводиться не будет. Допустимо задавать либо параметр text, либо icon, но не оба вместе; ♦ оп _press - ссы.rка на обработчик события «щелчок на кнопке»; или ВМР.
Урок ♦ 20. Разработка графических приложений enaЫed - 347 если True, кнопка будет доступна для взаимодействия, если False - недоступна. Пример: from os.path import dirname, join add_icon_path = join(dirname( file ), 'add.png') button = toga.Button(icon=add_icon_path, on_press=add_address) Класс вutton, представляющий кнопку, поддерживает свойства text и icon, храня­ щие соответственно надпись и путь к значку. 20.3.7. Графический элемент Графический элемент, выводящий изображение из файла с заданным путем, пред­ ставляется классом ImageView. Вот формат вызова его конструктора: ImageView(<пyть к файлу с выводимым изображением>) Путь задается в виде строки. Поддерживаются форматы JPEG, PNG, GIF, ТIFF иВМР. Пример: image_path = join(dirname( file ), 'some-irnage.jpg') image = toga.ImageView(image_path) Класс ImageView поддерживает свойство image, хранящее путь к файлу с выводи­ мым изображением. 20.3.8. Поле для ввода целого числа Поле для ввода целого числа с кнопками, уменьшающими и увеличивающими занесенное значение на заданный шаг, представляет класс NurnЬerinput. Его конст­ руктор вызывается в формате: NurnЬerinput([step=l] [, ] [min=None] [, ] [max=None] [, ] [value=None] [, [readonly=False] [, ] [on_change=None]) Параметры: ♦ step - шаг, на который будет уменьшено или увеличено занесенное значение при каждом щелчке на одной из кнопок, в виде целого числа; ♦ min - наименьшее значение, доступное для занесения. Если задать None, в поле можно занести любое число; ♦ max - наибольшее значение, доступное для занесения. Если задать None, в поле можно занести любое число; ♦ value - изначальное значение, которое должно присутствовать в поле. Если за­ дать None, поле изначально будет пустым.
Часть 348 111. Практическое Руthоп-программирование В эти три параметра значения можно заносить в виде целых, вещественных чи­ сел, чисел типа из модуля Decirnal decirnal или даже строк, содержащих числа. Вещественные числа будут автоматически округлены до целых. Назначение параметров readonly и on_ change описано в разд. 20.3.3. Пример: toga.NurnЬerinput(value=lO, input = rnin=O, rnax=lOO, step=S) Класс NurnЬerinput поддерживает свойство value, хранящее занесенное значение в виде объекта типа Decirnal из модуля decirnal. Такое значение будет преобразовы­ ваться в целый или вещественный тип автоматически при выполнении арифмети­ ческих действий с ним. Поле для ввода пароля 20.3.9. Поле для ввода пароля, скрывающее вводимые символы, представляет класс Passwordinput. Его конструктор вызывается в том же формате, что и конструктор класса тextinput (см.разд. 20.3.1 О. 20. 3. 3), и поддерживает те же свойства. Регулятор Регулятор представляется классом Slider. Формат вызова его конструктора: Slider( [value=None] [, ] [rnin=0.0] [, ] [rnax=l.O] [, ] [tick_count=None] [, [on_change=None] [, ] [on_press=None] [, ] [on_release=Mone] [, ] [enaЫed=True]) Параметры: ♦ value - изначальное значение для установки регулятора в виде вещественного числа. Если задать None, регулятор будет установлен на середину шкалы; ♦ rnin - ♦ rnax ♦ начальное значение шкалы регулятора в виде вещественного числа; конечное значение шкалы регулятора в виде вещественного числа; tick_ count - количество делений шкалы регулятора в виде целого числа. Если задать None, шкала не будет иметь делений; ♦ on_ change - ссьmка на обработчик события «изменение значения, заданного регулятором», неважно ♦ on_press - - выполненное пользователем или программно; ссылка на обработчик события «нажатие кнопки мыши на ручке регулятора перед ее перемещением»; ♦ on_release - ссылка на обработчик события «отпускание ранее нажатой кнопки мыши после перемещения ручки регулятора»; ♦ enaЫed - False - если тrue, регулятор будет доступен для взаимодействия, если недоступен. Пример: slider = toga.Slider(value=BO.O, rnin=l.O, rnax=lOO.O, tick_count=lOO)
Урок 20. Разработка графических приложений 349 Класс Slider подцерживает следующие полезные свойства: ♦ текущее значение, выставленное на регуляторе, в виде вещественного value числа; ♦ tick_value - текущее значение, выставленное на регуляторе, измеренное в де­ лениях. Находится в диапазоне от 1 до количества делений, заданного парамет­ ром tick _ count конструктора. Если регулятор не имеет делений, хранит None. 20.4. Модели и списки Модели 20.4.1. Модель Toga - хранилище данных, предназначенных для вывода в элементе управ­ ления спискового типа (раскрывающемся списке или таблице). Организовано в виде таблицы из строк и атрибутов. Атрибут Toga - столбец модели. Должен иметь уникальное имя. Имена атрибутов задаются при создании модели. Модель является объектом класса ListSource из модуля toga. sources. Конструктор этого класса имеет следующий формат вызова: ListSource(accessors=<пocлeдoвaтeльнocть имен а,zрибутов>[, data=None]) Имена атрибутов в заданной последовательности должны быть записаны в виде строк. В параметре cta ta указываются данные, которые должны быть занесены в создавае­ мую модель, в виде: ♦ последовательности словарей - каждый словарь будет преобразован в строку модели, хранящую значения элементов из этого словаря. Имена атрибутов в за­ данной последовательности должны совпадать с ключами элементов указанного словаря. Пример: >>> frorn toga.sources irnport ListSource >>> rnodell ListSource( accessors= ( 'narne' , 'version' ) , data= ( ( 'narne': 'Python', 'version': '3.13' ), { 'narne': 'JavaScript', 'version': '2024'), {'narne': 'РНР', 'version': '8.1') ♦ последовательности из вложенных последовательностей - каждая вложенная последовательность будет преобразована в строку модели, хранящую ее эле­ менты. Имена атрибутов в заданной последовательности в этом случае можно выбрать произвольно. Первый атрибут будет использован для доступа к первому элемен-
Часть 350 111. Практическое Руthоп-программирование ту вложенной последовательности, второй атрибут для доступа ко второму - элементу и т. д. Пример: >>> model2 ♦ ListSource( accessors= ( 'name', 'version' ) , data= ( ('Python', '3.13'), ('JavaScript', '2024'), ( ' РНР' , ' 8 . 1 ' ) последовательности из элементарных значений - каждое значение будет пре­ образовано в строку из единственного элемента. В последовательности имен атрибутов необходимо указать единственный атрибут, который будет приме­ няться для доступа к единственному значению. Пример: >>> model3 = ListSource(accessors=('name',), data=('Python', 'JavaScript', 'РНР')) Если в параметре ctata указать None, будет создана пустая модель, не содержащая данных. Доступ к отдельным строкам модели осуществляется по их индексам (поскольку модель имеет функциональность последовательности к значениям отдельных атрибутов строки - - см. разд. 10.2.5), а доступ посредством одноименных атрибутов объекта этой строки: >>> Обращаемся ко второй строке модели >>> row = model2[1] >>> row <Row lfb3200c770 name='JavaScript' version='2024'> >>> Извлекаем значения атрибутов пате и version этой строки >>> row.name, row.version ( 'JavaScript', '2024') Значения атрибутов строки можно изменять: >>> row.version = '2021' >>> row <Row lfb3200c770 name='JavaScript' version='2021'> Также можно удалять строки из модели: >>> len(modell) 3 >>> del modell[2] >>> len(modell) 2 Класс ListSource поддерживает методы append (), insert () (что позволяет добав­ лять строки в модель) и clear (), характерные для списков (см. разд. 5.1.2.2):
Урок 20. Разработка графических приложений 351 >>> len (model3) 3 >>> model3. append ( 'Java') >>> len (model3) 4 20.4.2. 20.4.2.1. Списковые элементы управления Раскрывающийся список Раскрывающийся список представляется классом Selection. Конструктор его клас­ са вызывается в формате: Selection( [items=None] [, ] [accessor=None] [, ] [value=None] [, ] [on_change=None] [, ] [enaЫed=True]) Параметры: ♦ данные, которые должны выводиться в списке, в виде либо готовой i tems - модели Listsource, либо в одном из форматов, указываемых в параметре data конструктора класса ListSource (см.разд. ♦ 20. 4.1); имя атрибута модели, значения которого будут выводиться в списке, accessor - в виде строки; ♦ value - ссылка на строку модели, которая должна быть выбрана в списке изна­ чально. Если задать None, будет выбрана первая строка; ♦ ♦ ссылка на обработчик события «выбор строки в списке»; on_ change enaЬled - если True, список будет доступен для взаимодействия, если False - недоступен. Пример: source = ListSource( accessors= ( 'name', 'version') , data= ( ('name': 'Python', 'version': '3.13'}, ( 'name': 'JavaScript', 'version': '2021'}, ('name': 'РНР', 'version': '8.1'} def sel_change(sel): output.text = sel.value.version languages = toga.Selection(items=source, accessor='name', value=source[-1], on_change=sel change) output = toga.Label(' ') Класс Selection поддерживает свойства value и items, хранящие ссылку на выбран­ ную строку модели и саму выводимую модель соответственно.
Часть 352 20.4.2.2. 111. Практическое Руthоп-программирование Таблица Таблица представляется классом таые. Вот формат вызова его конструктора: [, ] [data=None] [, ] [accessors=None] [, ] [multiple_select=False] [,] [on_select=None] [,] [on_activate=None] [, [missing_value=' ']) TaЬle([headings=None] Параметры: ♦ headings - последовательность заголовков столбцов, которые будут выводиться в шапке таблицы. Если указать None, таблица не будет иметь шапки; ♦ data - данные для вывода в таблице в виде либо готовой модели Listsource, либо в одном из форматов, указываемых в параметре data конструктора класса ListSource (см.разд. ♦ accessors - 20.4. ]); последовательность имен атрибутов, которые будут выводиться в таблице: ♦ rnultiple select - если тrue, в таблице можно будет выбрать произвольное ко­ личество строк, если ♦ ♦ False - лишь одну строку; ссьmка на обработчик события «выбор строки (строк) в таблице»; on select - ссылка на обработчик события «двойной щелчок на строке таб­ on_activate - лицы>>. Этот обработчик должен принимать два параметра: ссылку на таблицу, в которой возникло событие, и ссьmку на объект строки, на которой бьm выпол­ нен двойной щелчок; ♦ rnissing_value - значение, которое будет выводиться в ячейке таблицы, если соответствующий атрибут модели хранит None. Пример (рис. 20.3): def tab_select(tab): output.text = tab.selection.version languages = toga.TaЬle(headings=('Haзвaниe языка', 'Версия'), data=source, accessors=('narne', on- select=tab- select) output 'version'), toga.Label('') Название языка Python Версия 3.13 JavaScript 2021 РНР 8.1 Рис. 20.3. Таблица Класс таые поддерживает ряд полезных свойств и методов: ♦ selection брать: свойство (только для чтения), хранит, если таблица позволяет вы­
Урок 20. ссьтку на выбранную строку: • лишь одну строку • произвольное количество строк - - список ссылок на выбранные строки; свойство, ссылка на выводимую модель; ♦ da ta - ♦ scroll _ to _ top () - ♦ scroll _ to_bottorn () - ♦ 353 Разработка графическихприложений метод, прокручивает текущую таблицу до самого верха; метод, прокручивает текущую таблицу до самого низа; метод, прокручивает текущую таблицу до scroll _ to _ row (<иJЩекс строки>) строки с указанным иJЩексом. 20.5. Контейнеры 20.5.1. Стопка Контейнер-стопка выстраивает помещенные в него элементы окна в ряд по гори­ зонтали или вертикали в зависимости от заданных настроек стиля. Он представля­ ется классом вох, конструктор которого вызывается в следующем формате: Box([children=None]) В параметре ♦ children задается одно из двух: последовательность ссылок на элементы, помещаемые в создаваемый контей­ нер: rnain Ьох toga.Box(children=(inches_label, inches_input, button, crns label, crns_output)) ♦ ссылка на единственный элемент, помещаемый в контейнер. Управлять направлением выстраивания элементов позволяет свойство direction стиля контейнера. Значение 'row' указывает выстроить элементы по горизонтали (поведение по умолчанию), значение направления см. в листинге 'colurnn' - по вертикали (пример указания 20.3). Метод add () класса Вох добавляет заданные элементы в текущий контейнер: аdd(<элемент 1>, <элемент 2>, . . . , <элемент N>) Контейнер может содержать другие контейнеры, в которых указано другое направ­ ление выстраивания элементов (рис. 20.4): rnain_box = toga.Box() rnain_box.style.direction = 'colurnn' inches _ label = toga. Label ('Дюймы: ') inches_input = toga.Textinput(value=O) boxl = toga.Box(children=(inches label, inches_input)) button = tоgа.Вuttоn('Преобразовать')
Часть 354 111. Практическое Руthоп-программирование cms label = toga. Label ('Сантиметры: ') cms_output = toga.Textinput(value=0, readonly=True) Ьох2 = toga.Box(children=(cms_label, cms_output)) main_box.add(boxl, button, Ьох2) Дюймы : о Преобразовать Сантиметры : о Рис. 20.5.2. 20.4. Контейнер, содержащий другие контейнеры Блокнот с вкладками Блокнот с вкладками, между которыми можно переключаться щелчками на их ярлыках, представлен классом OptionContainer. Формат вызова его конструктора: OptionContainer([content=None] [, ] [on_select=None]) Параметров здесь всего два: ♦ content - описание набора вкладок для создаваемого блокнота в виде последо­ вательности из кортежей, содержащих от двух до четырех следующих эле­ ментов: • надпись для ярлыка - • содержимое вкладки в виде строки; - в виде ссылки на элемент окна, который должен на ней находиться (как правило, здесь указывается ссылка на какой-либо кон­ тейнер, содержащий другие элементы); • значок для ярлыка - в виде пути к файлу с этим значком. Если элемент от­ сутствует или содержит значение None, ярлык не будет содержать значка; • тrue, чтобы сделать вкладку доступной для выбора, или False - чтобы сде­ лать ее недоступной. Если элемент отсутствует, вкладка будет доступна для выбора; ♦ on_ select - ссылка на обработчик события «выбор вкладки в блокноте». Пример: name label = toga.Label('Имя: ') name_input = toga.Textinput() name Ьох = toga.Box(children=(name_label, name_input)) name_box.style.direction = 'column' password_label = toga.Label('Пapoль: ') password_input = toga.Passwordinput() password_box = toga.Box(children=(password_label, password_input)) password_box.style.direction = 'column'
Урок 20. 355 Разработка графических приложений ос= toga.OptionContainer(content=(('Имя', name_box), password_box, None, False))) ('Пароль', Класс Optioncontainer поддерживает свойство current _ tab, хранящее ссылку на вкладку, выбранную в текущий момент. Вкладка представляется объектом класса optionitem, который содержит следующие полезные свойства: ♦ index - индекс вкладки в блокноте; ♦ enaЫed - ♦ text - если тrue, вкладка доступна для выбора, если False - недоступна; текст на ярлыке вкладки. Пример: def oc_select(oc): ind = oc.current tab.index if ind == О # Выбрана первая вкладка elif ind == 1 # Выбрана вторая вкладка ос= on_select=oc_select) toga.OptionCoпtainer( 20.5.3. Панель с прокруткой Панель с прокручивающимся содержимым представляется классом ScrollContainer. Конструктор этого класса вызывается в формате: ScrollContainer ( [horizontal=True] [, ] [vertical=True] [, [on_scroll=None] [, ] [content=None]) Параметры: ♦ horizontal - если True, в панели будет доступна прокрутка по горизонтали, если False - не будет; ♦ vertical - если тrue, в панели будет доступна прокрутка по вертикали, если False - не будет; ♦ on_ scroll - ♦ content - обработчик события «прокрутка содержимого панели»; ссьшка на элемент окна, который следует поместить в панель (обыч­ но это контейнер, содержащий различные элементы). Пример (рис. 20.5): image_path = join(dirname( file ), 'image.jpg') image = toga.ImageView(image_path) sc = toga.ScrollContainer(content=image) Класс ♦ scrollContainer поддерживает следующие полезные свойства: horizontal_position го числа в пикселах; текущая позиция прокрутки по горизонтали в виде цело­
Часть 356 Рис. ♦ 20.5. 111. Практическое Руthоп-программирование Панель с прокруткой текущая позиция прокрутки по вертикали в виде целого vertical_position числа в пикселах; ♦ rnax _ horizontal _posi tion - максимально доступная позиция прокрутки по гори­ зонтали в виде целого числа в пикселах (только для чтения); ♦ rnax _ vertical _posi tion - максимально доступная позиция прокрутки по верти­ кали в виде целого числа в пикселах (только для чтения). 20.6. Задание стилей у элементов окон Встроенный стиль, задающий оформление элемента окна, хранится в свойстве style объекта этого элемента. Стиль представлен объектом класса style, поддер­ живающим следующие свойства: ♦ width - ширина элемента в виде целого числа в пикселах. Если задать 'none ', ширина элемента станет такой, чтобы только вместить его содержимое. По умолчанию: ♦ 'none'; высота элемента в виде целого числа в пикселах. Если задать 'none ', height - высота элемента станет такой, чтобы только вместить его содержимое. По умол­ чанию: 'none '; ♦ rnargin - величина просвета между границами элемента и границами его роди­ теля и соседей в пикселах. Может быть указана в виде: • • целого числа - задаст просветы со всех сторон; кортежа одного из следующих форматов лять собой целые числа: (<сверху и снизу>, <с.лева (<сверху>, <с.лева (<сверху>, <справа>, и справа>) и справа>, <снизу>, <снизу>) <с.лева>) Значение по умолчанию: О. Пример: rnain_box.style.rnargin (10, 30, 10, 50) - все элементы должны представ­
Урок ♦ 357 20. Разработка графических приложений margin_top, margin_right, margin_bottom и margin_left -величины просвета меж­ ду границей элемента и границей его родителя или соседа в пикселах соответст­ венно сверху, справа, снизу и слева; ♦ text align - выравнивание текста в элементе: 'left' (по левому краю), 'center' (по центру), 'right' (по правому краю) или 'justify' (полное вырав­ нивание). По умолчанию: 'left •; ♦ цвет текста в любом из форматов, поддерживаемых color - CSS. Если задать значение None, будет использован системный цвет. По умолчанию: None; ♦ background_ color - цвет фона в любом из форматов, поддерживаемых CSS. задать значение 'transparent ', будет задан прозрачный фон, а если None - Если фон системного цвета. По умолчанию: None. Пример: colored_box.style.color = '#eeeeff' colored_box.style.background_color = 'darkgrey' ♦ font family - список названий шрифтов, которыми будет выводиться текст в элементе. Используется первый из перечисленных в списке шрифтов, который удалось обнаружить в системе. В списке можно указать как непосредственно название шрифта, так и одно из следующих специальных обозначений: 'serif' (шрифт с засечками), 'sans- serif' (без засечек), 'cursi ve ', 'fantasy' (декоративные шрифты), 'monospace' (моноширинный) и 'system' (системный шрифт). Значение по умолчанию: ♦ font_size - [ 'system' J. кегль (размер) шрифта в виде целого числа в типографских пунк­ тах. Если задать число -1, будет использован системный кегль. По умолчанию: -1. Пример: header.style.font family = ['Verdana', header.style.font size = 42 ♦ font_weight - 'Times New Roman', 'cursive'] насыщенность шрифта: 'normal' (обычная) или 'bold' (полужир­ ный шрифт). По умолчанию: 'normal'; ♦ font style - начертание шрифта: 'normal' (обычное) или 'italic' (курсивное). По умолчанию: 'normal '; ♦ direction - у контейнера-стопки (см. разд. 20.5.1) - направление выстраива­ ния элементов: 'row' (по горизонтали) или 'column' (по вертикали). По умолча­ нию: ♦ gap - 'row"; у контейнера-стопки - величина просвета между элементами в виде целого числа в пикселах (по умолчанию: о); ♦ justify_content - у контейнера-стопки - выравнивание элементов вдоль на­ правления их выстраивания: 'start' (по левому краю), 'center' (по центру) или 'end' (по правому краю). По умолчанию: 'start ';
Часть 358 ♦ align_items - у контейнера-стопки - 111. Практическое Руthоп-программирование выравнивание элементов перпендику­ лярно направлению их выстраивания: 'start' (по верху), 'center' (по центру) или 'end' (по низу). По умолчанию: 'start'; ♦ flex - у элемента, находящегося в контейнере-стопке, - доля свободного про­ странства в контейнере, выделяемого под размещение этого элемента. Задается в виде вещественного числа. Если задать значение о. о, элемент получит размер, заданный в свойствах width или height, а если он не задан - такой, чтобы лишь вместить свое содержимое. По умолчанию: о. о; ♦ visiЬili ty - видимость элемента на экране: 'visiЫe' (элемент видим) или 'hidden' ( элемент невидим, однако под него все равно выделяется место в окне). По умолчанию: 'visiЫe'. 20. 7. Управление окнами 20.7.1. Задание главного окна приложения Если при программировании использовать подход, описанный в разд. 20.2.1, при­ ложение само создаст главное окно и задаст его настройки. Которые могут не удов­ летворить нашим требованиям. Однако мы сами можем создать для приложения главное окно, задав у него нужные нам настройки (размеры, возможность их менять, местоположение и др.). Только для этого придется реструктурировать код приложения. Прежде всего, необходимо объявить собственный класс приложения, сделав его производным от класса Арр. В этом классе следует объявить метод startup (self), в теле которого создать объект главного окна, обязательно присвоить ссылку на этот объект атрибуту rnain_ window объекта приложения и вывести окно на экран. Далее останется лишь создать объект нового класса приложения, не указывая в вы­ зове конструктора параметр startup (который сейчас уже не нужен). Главное окно представляется классом MainWindow. Его конструктор вызывается в следующем формате: MainWindow( [title=None] [, ] [position=None] [, ] [size=(640, 480)] [, ] [resizaЫe=True] [, ] [closaЫe=True] [, ] [rninirnizaЫe=True] [, [on_close=None] [, ] [on_gain_focus=None] [, ] [on_lose_focus=None] [, ] [on_show=None] [, ] [on_hide=None]) Параметров здесь много: ♦ title - ♦ position - текст заголовка окна. Если задано None, окно получит заголовок «Toga»; местоположение окна в виде кортежа из двух элементов, задающих горизонтальную и вертикальную координаты левого верхнего угла окна относи­ тельно левого верхнего угла экрана в виде целых чисел в пикселах. Если задано None, окно будет иметь случайное местоположение; ♦ size - размеры окна в виде кортежа из двух элементов, задающих ширину и высоту окна в виде целых чисел в пикселах;
Урок ♦ 20. Разработка графических приложений ♦ если resizaЫe - False - тrue, 359 пользователь может изменять если окна, размеры не может; не мо­ если True, пользователь может закрыть окно, если False - closaЫe жет ; если True, пользователь может свернуть окно, если False - ♦ minimizaЫe - не может; ♦ ссылка на обработчик события «закрытие окна пользователем» . Об­ on _ close - работчик должен возвращать значение True, чтобы разрешить закрытие окна, или False - чтобы запретить его; ♦ on_gain_ f ocus - ссылка на обработчик события «получение окном фокуса ввода» ; ♦ on_lose_focus - ссылка на обработчик события «потеря окном фокуса ввода»; ♦ on_ show - ссылка на обработчик события «вывод окна на экран»; ♦ on_hide - ссылка на обработчик события «временное скрытие окна». Содержимое главного окна можно задать, присвоив ссылку на элемент, который должен в нем находиться (обычно это контейнер с различными элементами), свой­ ству c ontent окна. Вывод окна на экран выполняется вызовом метода show () класса MainWindow. Исправим наше приложение преобразователя величин (см. листинг 20.3), задав у его окна уменьшенные размеры и убрав возможность их изменять. Код исправ­ ленного приложения приведен в листинге 20.4. Лмстмнr 20.4. Приложение nреобра3онтеnя нnичин с и3мененным окном (мсnравnения) class ConV8rtorApp (toga .App) : def _Ьuild_main_window(self) : def c onvert (button ) : cms_output. value = float (inches_input. value ) * 2.54 # 1 # 2 # # 3 # 5 main_box = t oga.Box() r e t u rn main Ьох def startup(self) : self.шain window = toga.М&inWindow( ti tle= 'Преобраsо■а'1'8JtЬ аепичин' , siz-(300, 170), resizaЫe=False self . шain window.content = self._build_main_window() sel!. main_ window. show () арр • Conv.rtorApp ( 'Преобраsоаа~ 11&1JИЧИН' , 'python-22-lessons.conV8rtor') 4
Часть 360 111. Практическое Руthоп-программирование Код, создающий интерфейс главного окна, мы для удобства вынесли в метод _build_main_window () класса приложения (поз. 1 в листинге 20.4). В теле метода startup () того же класса мы создаем главное окно и присваиваем ссылку на него атрибуту main_window (поз. Далее вызываем метод _build_main_ window ( J и присваиваем возвращенную им ссылку на контейнер с содержимым свойству content главного окна (поз. 3). После чего выводим окно на экран вызо­ вом метода show() (поз. 2). 4). Напоследок создаем объект нового класса приложения (поз. 20.7.2. Toga 5). Создание вторичных окон предоставляет возможность создавать вторичные окна для вывода какой-либо информации или получения данных от пользователя. Вторичное окно представляет класс window. Его конструктор имеет такой же фор­ мат вызова, что и конструктор класса MainWindow (см.разд. 20. 7.1). Класс Window (а также класс мainWindow) поддерживает следующие свойства: ♦ ♦ ti tle - текущий текст заголовка окна; state - текущее состояние окна в виде одного из элементов перечисления WindowState ИЗ модуля • NORМAL - • MINIMIZED- • МAXIMIZED • toga. constants: обычное состояние; - окно свернуто; окно развернуто; FULLSCREEN - полноэкранный режим ( окно занимает весь экран, не имеет заголовка и границ, однако главное меню и панель инструментов видимы); • PRESENTATION - режим презентации (то же самое, что и полноэкранный режим, только без главного меню и панели инструментов); ♦ closed - если True, окно уже закрыто, и False - если еще нет (только для чте- ния). Методы, поддерживаемые классом Window (и MainWindow): ♦ show ( ) - ♦ hide ( J ♦ close () - выводит текущее окно на экран; скрывает текущее окно без его закрытия; закрывает текущее окно (при этом обработчик события «закрытие окна», заданный в параметре on_ close конструктора, не вызывается). В программе можно создать произвольное количество вторичных окон и обмени­ ваться с ними данными. В качестве примера напишем небольшую программу с главным окном, содержа­ щим таблицу из одного столбца, и с кнопкой, выводящей вторичное окно. Вторич­ ное окно будет содержать поле ввода и кнопку, по нажатию которой строка, зане­ сенная в поле ввода, добавляется в таблицу (листинг 20.5).
Урок 20. Разработка графических приложений 361 import toga from toga.sources import ListSource class MultiWindowApp(toga.App): def startup(self): self._model = ListSource(accessors=('value',)) self. add window = None self.main_window = toga.MainWindow( titlе='Многооконное size=(З00, 170), # 1 2 3 4 # 5 # 6 # 7 8 9 # приложение', resizaЬle=False self.main window.content self.main window.show() self. build main window() def _build_main_window(self): self. tаЫе = toga.TaЬle(data=self._model, accessors= ( 'value', ) ) button = tоgа.Вuttоn('Добавить строки', on_press=self._show_add_window) main Ьох = toga.Box(children=(self. tаЫе, button)) main_box.style.direction = 'column' main_box.style.margin = 10 main_box.style.gap = 10 return main Ьох def # # show_add_window(self, button): if not self. add window: self. add_window = toga.Window( titlе='Добавление # # строк', size=(200, 70), resizaЬle=False, on close=self. on add window close self. add window.content sel f. add window. show () self. build_add_window() # 10 def on_add_window_close(self, window): self. add window = None return True # 11 # 12 # 13 def build_add_window(self): self._value = toga.Textinput() button = tоgа.Вuttоn('Добавить', on_press=self._add_value) Ьох = toga.Box(children=(self._value, button)) box.style.direction = 'column' box.style.margin = 10 # 14
Часть 362 box.style.gap return Ьох 111. Практическое Руthоп-программирование 10 # 15 def _add_value(self, button): self._rnodel.append(self._value.value) арр = MultiWindowApp('Мнoгooкoннoe приложение', 'python-22-lessons.rnulti-window') app.rnain_loop () Здесь мы снова применяем подход, описанный в разд. 20. 7.1 зать свое главное окно с нужными нам настройками, ния, производный от класса Арр (поз. 1 в листинге 20.5). В теле метода startup () класса приложения (поз. и позволяющий ука­ объявляем класс приложе­ сначала создаем модель для 2) таблицы, которая будет отображаться в главном окне, и присваиваем ссылку на нее атрибуту чтобы потом иметь к ней доступ (поз. _rnodel, 3). Далее создаем атрибут _add_ window, в котором потом будет храниться ссылка на вторичное окно, и пока присваиваем ему значение None (поз. описанным в разд. 4). После чего создаем главное окно способом, 20. 7.1. В теле метода (поз. создаем таблицу из одного столбца и связываем ее с ранее созданной моде­ 5), _bui ld_ rnain_ window (), лью, хранящейся в атрибуте _rnodel формирующего интерфейс главного окна (поз. 6). Также создаем кнопку Добавить стро­ ки, выводящую вторичное окно. По нажатию упомянутой кнопки будет вызван метод _show_add_window() (поз. 7). В его теле проверяем, хранится ли в атрибуте _add_window значение None (т. е. окно еще не создано - поз. 8), и если это так, то создаем окно (поз. экран вызовом метода show () (поз. 9) и выводим на 1О). Отметим, что в объявлении метода _show_add_window() (поз. 7) мы указали два па­ раметра: обязательную для всех методов ссылку на текущий объект (self) и ссылку на кнопку, в которой возникло событие. Не забываем, что в любой обработчик со­ бытия передается ссылка на элемент, в котором оно возникло, и эта ссылка в обра­ ботчик-метод как раз будет передана вторым параметром. При создании вторичного окна указываем в качестве обработчика события «закры­ тие окна» метод _on_ add_ window_ close () ( поз. 11 ). В его теле присваиваем атрибуту _add_ window значение None, тем самым сообщая программе, что окно закрыто, больше не существует и при следующем нажатии кнопки Добавить строки должно быть создано заново (поз. 12). И наконец, возвращаем из метода в качестве резуль­ тата значение тrue, разрешая окну закрыться (поз. В теле (поз. 14), метода _build_add_window(), 13). выводящего интерфейс вторичного окна создаем поле ввода и кнопку Добавить, добавляющую значение, занесен­ ное в поле, в модель и соответственно в связанную с ней таблицу.
Урок 20. Разработка графических приложений Метод 363 _add_value() (поз. 15) будет вызываться по нажатию кнопки Добавить вто­ ричного окна и добавлять введенное пользователем значение в модель. Запустим приложение и посмотрим, как оно работает. 20.7.3. Toga Вывод стандартных модальных окон позволяет выводить стандартные модальные окна: сообщения, предупрежде­ ния и др. Такое окно может быть модальным на уровне как отдельного окна, так всего приложения. Для вывода стандартного окна применяется сопрограмма-метод dialog () : diаlоg(<объект выводимого окна>) Чтобы сделать выводимое окно модальным на уровне: ♦ нужно вызвать упомянутую сопрограмму-метод у этого окна: отдельного окна - class SecondaryWindow(toga.Window): async def show_info(self): await self.dialog( toga.InfoDialog('Пpeoбpaзoвaтeль', 'Введите исходное ♦ всего приложения - значение') следует вызвать сопрограмму-метод у главного окна: await self.main_window.dialog( toga.InfoDialog('Пpeoбpaзoвaтeль', Объект выводимого 'Введите исходное значение') окна должен принадлежать одному из классов, представляющих различные виды стандартных окон. Каждый из этих классов обладает функцио­ нальностью фьючера. Вот эти классы: ♦ InfoDialog - окно-уведомление с заданным текстовым сообщением и кнопкой ОК: InfoDialog(<зaгoлoвoк окна>, <сообщение>) Результата не выдает; ♦ QuestionDialog - окно-запрос с заданным сообщением и кнопками Да и Нет. InfoDialog. Выдает значе­ в случае нажатия кноп­ ние тrue, если пользователь нажал кнопку Да, и False - Формат вызова конструктора такой же, как и у класса ки Нет. Пример: dlg = toga.QuestionDialog('Teлeфoннaя if await self.dialog(dlg): # ♦ книга', 'Удалить телефон?') Удаляем телефон ConfiпnDialog - окно-предупреждение с заданным сообщением и кнопками ОК и Отмена. Формат вызова конструктора такой же, как и у класса InfoDialog.
Часть 364 Выдает значение 111. Практическое Руthоп-программирование True, если пользователь нажал кнопку ОК, и False - в случае нажатия кнопки Отмена; ♦ ErrorDialog - окно с сообщением об ошибке. Полностью аналогично окну­ уведомлению, реализуемому классом InfoDialog. 20.8. Интеграция библиотек Toga и Tortoise ORM Заставить работать эти библиотеки вместе очень просто - работа одной из них никак не нарушает работу другой. Код, закрывающий соединение с базой данных, можно реализовать в виде сопро­ граммы и при создании объекта приложения указать ссылку на нее в параметре on_exit конструктора класса приложения (см.разд. 20.2.4): frorn rnodules.connection irnport арр = toga.App( . . . , dЬ_close, dЬ_init on_exit=dЬ_close) Теперь что касается кода, устанавливающего соединение с базой данных. Казалось бы, его также можно оформить в виде сопрограммы и задать ссылку на нее в пара­ метре арр on_ running конструктора класса приложения: = toga.App( . . . , on_running=dЬ_init) Но функция из параметра on_running будет вызвана лишь после вывода главного окна прwюженuя и запуска цикла обработки событий. Поэтому любая попытка прочитать данные из базы непосредственно при выводе главного окна (например, чтобы заполнить данными таблицу, отображающуюся в нем) будет обречена на неудачу, поскольку соединение с базой в этот момент еще не установлено. В таком случае придется соединяться с базой в обработчике события «вывод окна», задаваемом в параметре on show конструктора класса окна (см.разд. 20. 7.1). Там же можно и загрузить данные из базы. Пример: class SorneApp(toga.App): def startup(self): self.rnain window = toga.MainWindow( async def load_data(self, window): await dЬ_init () # Загружаем и выводим данные из on_show=load_data) базы 20.9. Что еще нужно знать о библиотеке ♦ предоставляет и другие элементы управления: поля для ввода значений Toga даты, времени, иерархический список (со Toga специализированной моделью), веб­ обозреватель, панели для вывода географических карт и рисования произволь-
Урок 20. 365 Разработка графических приложений ной графики. Также библиотека позволяет создавать у любого окна главное меню и панель инструментов, выводить значок в системном трее, поддерживает работу со встроенной веб-камерой и службами местоположения. Все эти инст­ рументы описаны в руководстве по библиотеке. ♦ К сожалению, Toga на текущий момент всё еще находится в разработке. Так, она не включает ряд полезных элементов окон (в частности, набора переключателей и обычного, не раскрывающегося списка) и не позволяет создавать собственные модальные диалоговые окна. ♦ Тем не менее Toga остается наиболее компактной, простой и удобной в исполь­ зовании библиотекой для программирования графических приложений. 20.1 О. 1. Самостоятельные упражнения В папке 20\!sources\phonebook сопровождающего книгу файлового архива (см. при­ ложение 4) находится код приложения телефонной книги, хранящего данные в базе и написанного с применением библиотек Toga и Tortoise ОRМ (см.урок 18). Просмотрите этот код и разберитесь, как он работает. 2. Добавьте в упомянутое приложение возможность фильтрации записей по вве­ денному фрагменту имени.
Урок 21 Математика Python предлагает впечатляющие инструменты для математических вычислений. Это в первую очередь встроенные функции (см.разд. math стандартной библиотеки (см. разд. 4.1.3.3). 4.1.3.2) и функции из модуля Также можно отметить средства для работы с комплексными числами (модуль cmath), с натуральными дробями (модуль fractions), вещественными числами фиксированной точности (тип Decimal из модуля decimal - они прекрасно подходят для обработки денежных сумм) и статистических вычислений (модуль statistics). Все эти инструменты описаны в документации по языку. Еще больше инструментов предлагают многочисленные дополнительные библио­ теки. На этом уроке мы кратко рассмотрим две из них. 21.1. Библиотека Дополнительная библиотека Matplotlib: Matplotlib вывод графиков предлагает мощные средства для вывода графиков самых разных типов: линейных, столбчатых и круговых диаграмм, гисто­ грамм и пр. - на основе заданных данных. Установить версию 3.10.* этой библио­ теки, описываемую в книге, можно подачей команды: pip install "matplotlib==З.10" На заметку Полное описание библиотеки Matplotlib находится по интернет-адресу: https://matplotlib.org/. Основные принципы 21.1.1. 21.1.1.1. Matplotlib Вывод простого графика Как и в случае с библиотеками NiceGUI и Toga, изучать Matplotlib мы будем на примере простой программы, которая выводит синусоиду. Для удобства написания на ее основе следующих программ разобьем ее код на два модуля. Создадим где-либо папку для хранения кода программы, в ней в ней - - папку modules, а пустой модуль _init_.py. Сначала напишем модуль modules\generators.py, в котором объявим два генератора­ функции, поставляющие значения для координатных осей Х и У будущих графиков (листинг 21.1 ).
Урок 21. 367 Математика frorn rnath irnport sin def xs(start, end, step=.01): current = start while current <= end: yield current current += step def ys(start, end, step=.01, func=sin): for х in xs(start, end, step): yield func(x) Функция xs () сформирует последовательность значений, находящихся в заданном диапазоне, для оси Х рисуемого графика. В качестве параметров она получит нача­ ло и конец диапазона и необязательное приращение. Функция ys () будет формировать аналогичную последовательность значений для оси У. Она получит те же параметры, что и функция xs (), плюс ссылку на функ­ цию, график которой следует вывести. Теперь займемся стартовым модулем тинг star1.py. Его код будет совсем коротким (лис­ 21.2). 21.2. Прогр~~~ график синуса, стартовый модуль start.py ----~ frorn rnatplotlib import pyplot frorn rnodules.generators import xs, ys figure, ахе = pyplot.suЬplots() axe.plot(tuple(xs(0, 20)), tuple(ys(0, 20))) pyplot. show () # # # 1 2 3 Сначала мы вызываем функцию subplots () из модуля matplotlib. pyplot, возвра­ щающую кортеж из двух значений: ссылки на объект, представляющий окно, в ко­ тором будет выведен график, и ссылки на объект набора координатных осей для выводимого графика (поз. 1 в листинге 21.2). Мы выполняем распаковку этого кор­ тежа в разные переменные ( о распаковке рассказано в разд. 5. 3). Далее рисуем график на полученных координатных осях, вызвав у объекта этих осей метод plot () (поз. 2). Метод plot () принимает в качестве параметров последовательности значений для координатных осей Х и У. Последовательность значений для оси Х мы сформируем вызовом генератора-функции xs () из написанного ранее модуля а последовательность значений для оси У - того же модуля. Проблема в том, что метод modules\generators.py, вызовом генератора-функции ys () из plot () не принимает генераторы в каче­ стве параметров, но мы без труда решим ее, преобразовав «на месте» генераторы в кортежи.
368 Часть 111. Практическое Руthоп-программирование И наконец, вызываем функцию show () из модуля matplotlib. pyplot, чтобы вывести окно с графиком на экран (поз. 3). Запустим программу и посмотрим на окно с нарисованным графиком (рис. Мы можем менять размеры этого окна - 21. l ). и график будет подстраиваться под них. Нажимая расположенные под графиком кнопки, можно регулировать масштаб гра­ фика, перемещать его, если он не помещается в окне, и даже сохранить в графиче­ ском файле. о Figure 1 х 1.00 0.75 0.50 0.25 0.00 -0.25 -0.50 -0.75 - 1.00 2.5 о.о Рис. 21.1.1.2. 21.1. 5.0 7.5 График 10.0 Matplotlib: 12.5 15.0 17.5 20.0 вывод синусоиды Вывод двух графиков на одних координатных осях На одних и тех же координатных осях можно вывести несколько графиков, нало­ жив их друг на друга. Иллюстрацией тому станет следующая программа, которая выведет графики синуса и косинуса. Она будет использовать модуль modules\ generators.py из предыдущей программы (см. листинг 21.1 ). Создадим папку для кода новой программы и скопируем в нее папку modules из предыдущей программы. После чего напишем стартовый модуль (листинг Листинг 21.3. Программа, выводящая накладывающиеся графики синуса и косинуса, стартовый модуль start.py from matplotlib import pyplot from math import cos from modules.generators import xs, ys figure, ахе = 21.3 ). pyplot.suЬplots()
Урок 21. 369 Математика axe.plot(tuple(xs(0, 20)) , tuple(ys(0, 20) ), tuple(xs(0, 20)), tuple(ys(0, 20, func=cos))) pyplot. show () Мы лишь добавили в вызов метода plot () еще два параметра - последователь­ ности значений для координатных осей Х и У второго графика . Линия второго графика будет выведена другим цветом (рис . _ 1 21.2). □ Figure1 х 1.00 0.75 0.50 0.25 0.00 -0.25 -0.50 - 0 .75 - 1.00 о. о 2.5 Рис . 5.0 21.2. Два 7.5 10.0 12.5 15.0 17.5 20.0 накладывающихся графика Для вывода двух графиков на тех же осях также можно написать два вызова метода plot () с разными наборами данных: axe.plot(tuple(xs(0, 20)), tuple(ys(0, 20))) axe.plot(tuple(xs(0, 20)), tuple(ys(0, 20, func=cos))) 21.1.1.3. Вывод двух графиков на разных координатных осях Вывести разные графики можно и на разных наборах координатных осей, располо­ женных рядом в окне. Это продемонстрирует наша новая программа, которая также будет использовать модуль тинг modules\generators.py из первой программы (см. лис­ 21.1). Листинr 21.4. Проrрамма, выводящая расположенные рядом графики синуса и косинуса, стартовый модуль start.py frorn rnatplotlib irnport pyplot frorn rnath irnport cos frorn rnodules .generators irnport xs, ys
Часть 370 111. Практическое Руthоп-программирование figure, (sin_axe, cos - ахе) = pyplot.subplots(nrows=2) = tuple(xs(0, 20)) sin_axe.plot(x, tuple(ys(0, 20))) cos_axe.plot(x, tuple(ys(0, 20, func=cos) ) ) pyplot. show () l # # # # х В вызове функции suЬplots () мы указали параметр nrows со значением 2 3 4 2, тем са­ мым предписав создать два набора координатных осей, расположенных друг под другом (поз. 1в листинге 21.4 ). В этом случае вторым элементом кортежа, возвра­ щенного функцией, станет кортеж со ссылками на созданные объекты осей. Мы распаковали этот кортеж в разные переменные, заключив их в круглые скобки. Чтобы нарисовать графики, достаточно вызвать у каждого из полученных объектов координатных осей метод plot (). Оба графика используют одинаковые последовательности значений для оси Х. Вместо того, чтобы формировать каждую из них при каждом вызове метода plot () , впустую тратя системные ресурсы, мы сформируем одну последовательность и со­ храним ее в переменной (поз. 2). После чего просто подставим переменную с этой последовательностью в оба вызова метода plot () (поз. Запустив программу, увидим два графика - 3 и 4). синусоиды и косинусоиды (рис. -1,,,. Figure 1 о 21.3 ). х 1.0 0.5 О. О -0.5 -1.0 о. о 2.5 5.0 7.5 10.0 12 .5 15.0 17.5 20.0 о. о 2.5 5.0 7.5 10.0 12.5 15.0 17.5 20.0 1.0 0.5 О.О - 0.5 -1.0 Рис. 21.3. Два графика, расположенные друг под другом 21.1.1.4. Настройка графиков Matplotlib предоставляет богатые возможности по настройке рисуемых графиков: задание цвета линий, легенды, заголовка, выводимого над графиком, и др. Поэкс-
Урок 371 21 . Математика периментируем с выводящую программу, одну еще написав ними, синусоиду и косинусоиду на одних и тех же осях. Не забудем поместить в ее состав модуль modules\generators.py из первой программы (см . листинг 21.1 ). Листинг 21.5. Программа, выводящая графики синуса и косинуса, с оформлением , стартовый модуль start.py from ma t plo tl ib impo rt pypl ot f rom math import cos from modu l es . generators impor t xs , ys f igure , ахе = pyp l ot . suЬpl o ts( ) = tupl e (xs(0 , 20)) axe . p l ot(x , t upl e(ys(0 , 20)) , l abel= ' si n (x) ', col o r ='red ', l inewidth=5 ) ' cos (х ) ' , = l labe , )) =cos unc f , 20 , О ( axe.plot (х , tuple (ys ) ' -' = linestyle ', Ыue colo r=' ахе. s e t xl abe l ( ' х ' ) a xe.s et_y l a be l ( ' s in lcos(x) ' ) х axe.se t_ti tle( ' C ин ycoи дa и к оси н ус ои да ' ) axe. gr i d(True ) ахе. l egend ( ) pyplot . s how ( ) # 1 # 2 # # # # # 3 4 5 6 7 При создании первого графика в вызове метода pl ot () координатных осей указы­ ваем текстовую метку для графика, которая будет выводиться в составе легенды о Figure 1 Синусоида и косинусоида ,, I 0.75 , ,r r j 1 1 1 1 0.25 х r 1 '' 1 1 1 1 1 1 1 1 1 1 1 0.00 с ·v1 -0.25 -0 .50 '' , r r r r \ О. О 2.5 1 1 1 1 ' 1 1 1 1 '~' 7.5 ' ' '~1 10.0 12 .5 15.0 х Рис . 21 .4. sin(x) cos(x) 1 1 '' ' 1 5,0 ' 1 1 \ .1 - 1 1 I \ 1 1 1 1 1 1 1 1 , \ , '' 1 1 1 1 ~ 1 1 L ' ' '' '' ' ' '' ' 1 1 1 1 1 1 1 1 //\•'. 1 / 1 \ r 1, -1 .00 1 1 1 1 r 1 - 0.75 \ , \ r 1 8 \ ltr 1 1 0.50 и ,,, 1 ,, 1.00 Графики с оформлением 17.5 20.0 х
Часть///. Практическое Руthоп-программирование 372 (параметр label), красный цвет линий (значение 'red' параметра color) и их тол­ щину в 5 типографских пунктов (параметр linewidth - поз. 1 в листинге 21.5). При создании второго графика также задаем текстовую метку, синий цвет линий (значе­ ние 'Ыuе' параметра color) и их пунктирную форму (значение linestyle - ПОЗ. ' ' параметра 2). Задаем текстовую метку у координатной оси Х вызовом метода set_ xlabel () (поз. 3), а у оси У - вызовом метода set _ylabel () (поз. 4) объекта осей. Заголовок у набора координатных осей указываем, вызвав метод set title () (поз. 5). Потом включаем вывод сетки, вызвав метод grid () с передачей ему значения тrue (поз. и выводим легенду вызовом метода legend () (поз. 6), 7). Вот теперь наши графики оформлены как следует (рис. 21.4 ). Создание координатных осей 21.1.2. Для создания наборов координатных осей применяется функция suЬplots () из мо­ дуля matplotlib.pyplot: suЬplots ( [nrows=l] [, ] [ncols=l] [, ] [ figsize= ( 6. 4, 4. 8) ] [, [facecolor='white']) Параметры: ♦ nrows - количество создаваемых наборов координатных осей, выстроенных по вертикали; ♦ ncols - количество создаваемых наборов координатных осей, выстроенных по горизонтали; ♦ figsize - размеры каждого создаваемого набора осей в виде кортежа из двух значений: ширины и высоты ♦ facecolor - - выраженных вещественными числами в дюймах; цвет фона создаваемого набора в виде строки в любом формате, поддерживаемом CSS. Функция возвращает кортеж из двух элементов: ♦ ссылки на объект класса Figure из модуля matplotlib. figure, представляющий окно, в котором будут выводиться графики; ♦ если: • был создан один набор осей (параметрам nrows и ncols даны значения 1) - ссьшка на этот набор. Набор осей представляется классом Axes из модуля matplotlib.axes; • было создано несколько наборов, расположенных по одной оси раметров nrows и ncols получил значение больше 1), - (один из па­ кортеж ссылок на созданные наборы осей; • бьшо создано несколько наборов, расположенных по двум осям метра nrows и ncols получили значения больше 1), - (оба пара­ кортеж с вложенными кортежами, содержащими ссьшки на созданные наборы осей.
Урок 21. Математика 373 Пример: from matplotlib import pyplot figure, avs = 21.1.3. pyplit.suЬplots(ncols=2, figsize=(B.O, 4.0), facecolor=' yel low' ) Рисование графиков разных типов Рассмотрим рисование графиков некоторых наиболее распространенных типов. Для этого применяются следующие методы класса Axes, представляющего набор осей: ♦ рисует линейный график или графики: plot () - рlоt(<даНl-/Ые для оси Х rрафика 1>, <данные для оси У rрафика 1>, <данные для оси Х rрафика 2>, <данные для оси У rрафика 2>, N>, <даНl-lЫе для оси N> [, ... , <даНl-/Ь/е для оси Х rрафика У rрафика label=None] [, color=None] [, linewidth=None] [, linestyle=None] [, alpha=None]) ДаНl-lЫе для осей задаются строго в виде готовой последовательности. Итераторы и генераторы не поддерживаются. Остальные параметры: • label - текстовое описание рисуемого графика, выводимое в составе леген­ ды. Если задать None, график не будет иметь описания; • color мом • • CSS. цвет линий графика в виде строки в любом формате, поддерживае­ Если задать None, библиотека сама задаст цвет линий; linewidth - толщина линий графика в виде вещественного числа в пунктах. Значение None задает минимальную толщину; linestyle - стиль линий графика в виде одного из следующих значений: или None (сплошные), ' ' (штриховые), '-.' (штрихпунктирные) или '-' '.' (пунктирные); • alpha - уровень непрозрачности рисуемого графика в виде вещественного числа от о. о (график полностью прозрачен) до 1. о (полностью непрозрачен). Значение None эквивалентно 1. о. Примеры рисования линейных графиков см. в разд. ♦ bar () - 21. 1. 1; рисует столбчатую диаграмму: bar (<даНl-/Ые для оси Х>, <значения высоты> [, width=O. В] [, bottom=O] [, align='center'] [, label=None] [, facecolor=None] [, edgecolor=None] [, linewidth=None] [, linestyle=None] [, alpha=None]) ДаНl-lЫе для оси х и значения высот задаются строго в виде готовой последова­ тельности. Остальные параметры: • width - либо единственное значение ширины всех столбцов рисуемой диа­ граммы, либо последовательность значений ширины каждого из столбцов.
Часть 374 111. Практическое Руthоп-программирование Каждое значение высоты задается в виде вещественного числа от о. о до 1. о и представляют собой долю от разницы между текущим и предыдущим зна­ чениями на оси Х; • либо единственное значение, задающее местоположение низа всех bottom - столбцов рисуемой диаграммы, либо последовательность местоположений низа каждого из столбцов. Все местоположения низа задаются в тех же еди­ ницах измерения, что и значения высоты; • align горизонтальное выравнивание столбцов: 'center' (по центру) или 'edge' (по левому краю); • facecolor - цвет заливки столбцов; • edgecolor - цвет границ столбцов. Назначение остальных параметров рассмотрено в описании метода plot (). Пример (рис. 21.5): figure, (axel, ахе2) = pyplot. subplots (nrows=2) axel .bar ( (1, 2, 3, 4, 5, 6), (7, 4, 1, 6, 2, 6)) (7, 4, 1, 6, 2, 6), ахе2 .bar ( (1, 2, 3, 4, 5, 6), width=(.8, .4, .2, .5, .1, .4), bottom=(-2, О, О, -1, -3, 2)) б 4 2 о 1 2 з 8 б 4 1 2 о -2 1 2 Рис. ♦ hist () • з 21.5. 5 4 б 1 1 4 5 б Столбчатые диаграммы -рисует гистограмму: hist(<входные данные>, <количество столбцов>[, cumulative=False) [, bottom=O) [, align='mid') [, orientation='vertical'] [, label=None] [, color=None] ) входные данные задаются строго в виде готовой последовательности, количество столбцов - в виде целого числа. Остальные параметры:
Урок • • 21. 375 Математика cumulative - если True, будет выведена гистограмма с накоплением, если False - обычная; align - выравнивание столбцов диаграммы: 'left' (по левому краю), 'mid' (по центру) или 'right' (по правому краю); • ориентация столбцов гистограммы: orientation - 'horizontal' (горизон- тальная) или 'vertical' (вертикальная); • color - цвет столбцов. Назначение остальных параметров рассмотрено в описаниях методов bar () и plot (). Пример (рис. 21.6): figure, ((axl, ах2), (ахЗ, ах4)) = pyplot.suЬplots(ncols=2, nrows=2) data = (2, 6, 4, 9, 2, 5, 7, 1, О, 7, 8, 3, 4, 8) axl.hist(data, 4) ax2.hist(data, 4, cumulative=True) axЗ.hist(data, 4, align='left') ax4.hist(data, 4, orientation='horizontal') 5 12 .5 4 10.0 з 7.5 2 5.0 1 2.5 о о.о о 2 б 4 8 5 8 4 б з 4 2 2 1 о о 2 4 б Рис. ♦ pie () - 8 21.6. 2 4 Гистограммы рисует круговую диаграмму: рiе(<данные для диаграммы>[, explode=None] [, labels=None] [, colors=None] [, shadow=False] [, radius=l]) данные для диаграммы задаются строго в виде готовой последовательности. Раз­ мер последовательности данных задаст количество секторов в диаграмме. Ос­ тальные параметры:
Часть 376 • 111. Практическое Руthоп-программирование последовательность величин, на которых каждый из секторов вы­ explode - нодимой диаграммы будет отстоять от ее центра. Каждая из величин задается в виде вещественного числа от о. о до 1. о и представляет собой долю радиуса диаграммы. Если задать None, все секторы будут начинаться с центра: • labels - последовательность текстовых надписей, выводимых возле каждого сектора диаграммы; • либо цвет, которым будут выведены все секторы диаграммы, либо colors - последовательность цветов ее секторов. Если задать None, цвет задаст сама библиотека; • • shadow - если True, диаграмма будет иметь тень, если False - не будет; radius - радиус диаграммы в виде целого или вещественного числа в дюй­ мах. Пример (рис. 21. 7): pyplot.suЬplots(ncols=2) figure, (axel, ахе2) axel.pie((3, 8, 5, 1), labels=('l', '2', '3', '4')) axe2.pie((3, 8, 5, 1), labels=('l', '2', '3', '4'), explode=(.2, .3, .4, .5), shadow=True) 4 Рис. 21.7. Круговые диаграммы Вывести несколько графиков на одном и том же наборе осей можно, вызвав соот­ ветствующий метод нужное количество раз (рис. figure, ахе = pyplot.suЬplots() datal = (2, 6, 4, 9, 2, 5, 7, 1, data2 = (3, 2, О, 5, 8, 1, О) axe.hist(datal, 4) О, 21.8): 7, 8, 3, 4, 8) axe.hist(data2, 4) При выводе линейных диаграмм можно просто записать соответствующее количе­ ство последовательностей в одном вызове метода plot () (пример см. в листинге 21.3).
Урок 21. Математика 377 5 4 з 2 1 о~....------т------.-----.----- о Рис. 21.1.4. 21.8. 2 4 б 8 Две гистограммы, выведенные на одном и том же наборе координатных осей Вывод и оформление графиков Вывод нарисованных графиков выполняется вызовом функции show () из модуля matplotlib. pyplot. Оформить график позволяют методы класса Axes, приведенные далее. Их следует вызывать перед вызовом функции show ( ) : ♦ set _ xlabel ( <надпись> [, loc=' center'] ) - задает надпись для оси Х. В параметре loc указывается местоположение надписи: 'left' (слева), 'center' (в центре) или 'right' (справа); ♦ sеt_уlаЬеl(<надпись>[, loc='center']) - задает надпись для оси У. Параметр loc устанавливает местоположение надписи: 'top' ( сверху), 'center' (в центре) или 'bottom' (снизу); ♦ set _ ti tle (<заголовок> [, loc=' center'] ) - указывает заголовок, выводимый над набором координатных осей. В параметре loc задается местоположение заголов­ ка по горизонтали: 'left' (слева), 'center' (в центре) или 'right' (справа); ♦ legend ( [ loc= 'best' J ) - выводит легенду, которая строится на основе текстовых описаний графиков, указанных в параметре label соответствующих методов (см. разд. 21.1.3). Параметр loc задает местоположение легенды: 'upper left' (в левом верхнем углу набора осей), 'upper center' (у верхней границы), 'upper right' (в правом верхнем углу), 'center left' (у левой границы), 'center' (в центре набора осей), 'center right' (у правой границы), 'lower left' (в ле­ вом нижнем углу), 'lower center' (у нижней границы), 'lower right' (в правом нижнем углу) или 'best' (местоположение выбирает библиотека); ♦ grid (<выводить сетку?> [, axis= 'both'] ) - управляет выводом сетки. Если пер­ вым параметром передать значение True, сетка будет выведена, если False скрыта. Параметр axis указывает, какие линии сетки должны быть выведены:
Часть 378 Практическое Руthоп-программирование ///. 'х' (только горизонтальные), 'у' (только вертикальные) или 'both' (и те и дру­ гие). Пример использования этих методов можно увидеть в листинге 21.5. Библиотека NumPy: обработка многомерных массивов 21.2. Дополнительная библиотека мощный инструмент для обработки много­ NumPy - мерных массивов. Массив 11 структура, предоставляемая библиотекой NumPy, аналогичная списку и содержащая числа. Python Версию 2.3.* этой библиотеки, описываемую в книге, можно установить подачей команды: pip install "numpy==2. 3" Эта библиотека автоматически устанавливается вместе с библиотекой 21.1), так что отдельно устанавливать ее не придется. Matplotlib (см.разд. На заметку Документация по библиотеке NumPy находится по интернет-адресу: https://numpy.org/. Создание массива array NumPy (<исходные данные>, выполняется функцией array () из модуля numpy: [<тип данных>=flоаt]) исходные данные задаются в виде кортежа из вложенных кортежей. Каждый вло­ женный кортеж представляет строку создаваемого массива. Тип данных задается в виде ссылки на соответствующий тип или один из типов, предоставляемых NumPy Python (int или float) и объявленных в модуле numpy: intl6 (16-разрядное знаковое целое число), uintl6 (16-разрядное беззнаковое целое чис­ ло), int32 (32-разрядное знаковое целое число), uint32 (32-разрядное беззнаковое целое число), uint64 (64-разрядное беззнаковое целое число), float32 (32-разрядное вещественное число), float64 (64-разрядное вещественное число) и др. Пример: >>> import numpy numpy.array(((31, 13, 2), (8, 111, 43), >>> arr numpy. uintlб) >>> arr 2], >>> array( [ [ 31, 13, 43], [ 8, 111, 4, 94]], dtype=uintlб) [ 76, Массив NumPy (76, 4, 94)), поддерживает те же операции, что и список >>>#Извлекаем строку массива по ее индексу >>>#получаем новLй массив NumPy, - Python. и в результате содержащий эту строку Проверим:
Урок 21. 379 Математика >>> arr[0] array ( [31, 13, >>>#Берем срез 2], dtype=uintl6) с первой по вторую строки массива >>>#и также получаем новый массив >>> arr[:2] >>> array( [ [ 31, 13, [ 8, 111, - с этими строками 22], 43]], dtype=uintl6) Выражение формата: <массив>[<операция взятия среза>, <индекс столбца>] вернет новый массив, содержащий лишь значения столбца с указанным индексом: >>>#Берем срез с первой по вторую строки массива, >>>#содержащий лишь значения второго столбца >>>arr[:2, 1] array([ 13, 111], dtype=uintl6) Поехали дальше: >>>#Извлекаем элемент строки массива »> arr[0] [2] np.uintl6(2) >>>#Изменяем элемент строки массива >>> arr[0] [2] = 22 >>> arr array ( [ [ 31, 13, [ 8, 111, 4, 76, 22], 43], 94]], dtype=uintl6) Получить доступ к элементу массива NumPy также можно, записав оба индекса внутри квадратных скобок через запятую: >>> arr[0, 2] np.int64(22) >>> arr[0, 2] = 2 Мы можем выполнять обычные арифметические и алгебраические операции с лю­ бым элементом массива и обычным числом: >>> arr[0] [2] + 2 np.uintl6(4) >>> from math import sqrt >» arr[l] [1], sqrt(arr[l] [1]) (np.uintl6(111), 10.535653752852738) А также с массивом и числом и с двумя массивами: >>> arr + 2 array( [ [ 33, 15, [ 10, 113, 78, 6, 24], 45], 96]], dtype=uintl6)
Часть 380 111. Практическое Руthоп-программирование >>> arr * 3 array ( [ [ 93, 39, 66], [ 24, 333, 129], (228, 12, 282]], dtype=uintl6) >>> arr / 4 array([[ 7.75, 3.25, 5.5 ], [ 2. , 27.75, 10.75], 1. , 23.5 ]]) (19. >>> arr2 = numpy.array( ( (5, 39, 21), (74, 9, 35), (58, 73, 4)), numpy.uintl6) >>> arr + arr2 array ( [ [ 36, 52, 43], [ 82, 120, 78], [134, 77, 98]], dtype=uintl6) >>> arr / arr2 array( [ [ 6.2 0.33333333, 1.04761905], 1.22857143], [ 0.10810811, 12.33333333, ]]) 1. 31034483, 0.05479452, 23.5 Доступны алгебраические, тригонометрические и логарифмические операции над массивом - с использованием функций из модуля numpy: >>>#Квадратный корень >>> numpy.sqrt(arr) array(([ 5.5677643, 3.6055512, [ 2.828427, 10.535654, 8. 7177 98 , 2. 4.690416 ], 6.5574384], 9.69536 ]], dtype=float32) >>>#Синус >>> numpy.sin(arr) array( [ (-0.40403765, 0.42016706, -0.00885131], [ 0.98935825, -0.8645514, -0.8317747 ], [ 0.56610763, -0.7568025, -0.24525198]], dtype=float32) >>> # Натуральный логарифм >>> numpy. log (arr) array([[3.4339871, 2.5649493, 3.0910425], [2. 0794415, 4.7095304, 3.7612002], [4.3307333, 1.3862944, 4.543295 ] ] , dtype=float32) >>> # Десятичный логарифм >>> numpy.loglO(arr) array ( [ [ 1. 4 913617, 1.1139433, 1.3424227], (0.90309 , 2.045323, 1.6334685], [ 1. 8808136, 0.60206 , 1.9731278]], dtype=float32) >>>#Экспонента (пришлось указать в параметре dtype более "емкий" >>>#тип данных float64 - иначе мы получили бы ошибку) >>> numpy.exp(arr, dtype=numpy.float64) array([[2.90488497e+l3, 4.42413392е+05, 3.58491285е+О9], [2.98095799е+О3, 1.60948707е+48, 4.72783947е+18], [1.01480039е+33, 5.45981500e+Ol, 6.66317622е+40]])
Урок 21. Математика 381 И многие-многие другие действия: >>>#Сумма всех элементов массива >>> nurnpy. surn (arr) np.uint64(402) >>>#Сумма элементов по строкам >>> nurnpy.surn(arr, axis=0) array([ll5, 128, 159), dtype=uint64) >>>#Сумма элементов по столбцам >>> nurnpy.surn(arr, axis=l) array([ 66, 162, 174), dtype=uint64) >>>#Наименьший элемент массива >>> nurnpy.min(arr) np.uintl6(4) >>>#Наименьшие элементы по строкам >>> nurnpy.min(arr, axis=0) array ( [ 8, 4, 22), dtype=uintl6) >>>#Наименьшие элементы по столбцам >>> nurnpy.min(arr, axis=l) array( [13, 8, 4), dtype=uintl6) >>>#Получаем все элементы массива, значения которых больше 50 >>> arr[arr > 50) array( [111, 76, 94), dtype=uintl6) >>> # Получаем все четные элементы массива >>> arr[arr % 2 О] array( [22, 8, 76, 4, 94], dtype=uintl6) >>> # Сравниваем элементы двух массивов >>> arr > arr2 array ( [ [ True, False, True], [False, True, True], [ True, False, True]]) А еще мы можем сохранять массивы в файлах и загружать их оттуда: >>>#Сохраняем массив в файле. Расширение npy добавляется автоматически >>> nurnpy.save(r'c:/math/nurnpy/array', arr) >>>#Загружаем массив из >>>#придется добавить >>> arrЗ файла. Расширение npy к имени файла явно = nurnpy.load(r'c:/math/nurnpy/array.npy') >>>#Загруженный массив полностью идентичен исходному >>> arr == arrЗ array ( [ [ True, [ True, True, True, True, True, True], True], True]])
Часть 382 21.3. Практическое Руthоп-программирование Что еще нужно знать о библиотеках ♦ 111. Matplotlib и NumPy Объекты разных типов, поддерживаемых библиотекой NumPy, могуr хранить значения разного размера и занимают разный объем памяти. Так, объект типа -32 768 до 32 767 и занимает 2 байта, значения в диапазоне от -2 147 483 648 до 2 147 483 647, 4 байта. А объект типа int, поддерживаемого Python, бу­ intlб может хранить значения в диапазоне от а объект типа int32 однако занимает уже дучи достаточно сложной структурой, занимает в памяти несколько десятков байтов. Выбрав подходящий тип значений при создании массива, можно существенно уменьшить объем занимаемой массивом памяти и ускорить его обработку. ♦ Библиотеки Matplotlib и NumPy предоставляют огромное количество инстру­ ментов буквально на все случаи жизни. Каждая из них достойна отдельной кни­ ги. Здесь же приведено лишь их краткое описание.
Урок 22 Искусственный интеллект Искусственный интеллект (ИИ) - общее название программ, имитирующих челове­ ческое мышление. Большая языковая модель конкретная программа, реализующая искусственный ин­ - теллект. НИ-клиент программа, предназначенная для взаимодействия с большой языковой - моделью: отправки ей вопросов, введенных пользователем, и вывода полученных от нее ответов. НИ-агент - дальнейшее развитие ИИ-клиента. Способен самостоятельно выполнять заданные действия (например, искать дополнительные сведения в Интернете и от­ правлять их языковой модели для обработки). Python позволяет разрабатывать такие ИН-клиенты и ИИ-агенты - как общего на­ значения, так и специализированные, настроенные на какую-либо область знаний. Мы будем использовать большую языковую модель GigaChat, разработанную ком­ панией «Сбер». К ней достаточно просто получить бесплатный доступ. 22.1. Регистрация в личном кабинете GigaChat API и получение ключа авторизации Для обращения к программному интерфейсу GigaChat (называемому GigaChat API) необходим электронный ключ авторизации. Получить его можно в личном кабине­ те GigaChat API. Откройте веб-обозреватель, перейдите в личный кабинет GigaChat 1 и выполните вход, следуя появляющимся на экране инструкциям. Если вы еще ни разу не входили в этот личный кабинет, для вас будет создан новый проект GigaChat. На экране появится диалоговый блок, в котором вы увидите имя проекта Мой GigaChat API, заданное по умолчанию, и при желании сможете его изменить. Далее нажмите кнопку Создать проект. После создания проекта к нему автоматически будет применен бесплатный тарифный план Freemium. Теперь вы можете получить ключ авторизации. Найдите на появившейся странице блок Доступ к API, а в нем страница Настройка кнопку Получить 1 API. ключ. - кнопку Получить доступ и нажмите ее. Откроется В ней отыщите раздел Ключ авторизации, а в нем В появившемся диалоговом https://developers.sber.ru/studio/workspaces/my-space/get/gigachat-api. блоке Сохраните - ваш
Часть 384 Authorization Кеу найдите поле 111. Практическое Руthоп-программирование Authorization Кеу, в котором отображается сгене­ рированный ключ авторизации. Щелкните на расположенной в правой части этого поля кнопке - и ключ будет скопирован в буфер обмена. Обязательно сохраните его где-либо (лучше всего в отдельном файле) и лишь после этого закрывайте диа­ логовый блок нажатием кнопки Готово. Дr~я того чтобы НИ-клиенты и ИИ-агенты, которые мы напишем, успешно работа­ ли, также необходимо установить на компьютер сертификаты безопасности НУЦ Минцифры. Перейдите на соответствующую страницу портала «Госуслуги» 1 и сле­ дуйте опубликованным там указаниям. 22.2. Библиотеки LangChain, langchain-community и langchain-gigachat Дr~я программирования НИ-клиентов и НИ-агентов мы используем три дополни­ тельные библиотеки: ♦ ♦ LangChain - основная библиотека, реализующая функциональность клиентов; langchain-community - инструменты для связывания LangChain с различными большими языковыми моделями, а также набор уже готовых «связок» подобно­ го рода2 ; ♦ langchain-gigachat - Установить версии 0.3 «связка» с GigaChat3. всех этих библиотек можно, подав в консоли три команды: pip install "langchain==0.3" pip install "langchain-community==0.3" pip install "langchain-gigachat==0.3" На заметку Полное описание библиотек LangChain, langchain-community и langchain-gigachat при­ ведено по интернет-адресам: https://python.langchain.com/docs/, https://python.langchain.com/api_reference/community/ https://developers.sber.ru/docs/ru/gigachain/overview. 22.3. Разработка ИИ-клиентов 22.3.1. Соединение с большой языковой моделью Соединение с большой языковой моделью са и GigaChat представляется объектом клас­ GigaChat из модуля langchain-gigachat.chat_models. Конструктор этого класса вызывается в следующем формате: 1 2 3 https://www.gosuslugi.ru/crt. «Связки» с GigaChat там, к сожалению, нет, и ее придется устанавливать отдельно. Эта библиотека использует для работы инструменты из библиотеки langchain-community, поэтому послед­ няя также должна быть установлена.
Урок 385 Искусственный интеллект 22. GigaChat(credentials=<ключ авторизации>[, scope='GIGACНAT_API_PERS'] [, model='GigaChat-2'] [, verify_ssl certs=True] [, streaming=F'alse] [, temperature=None] [, max_tokens=None]) Ключ авторизации, ранее полученный в личном кабинете GigaChat АР!, вводится в виде строки. Остальные параметры: ♦ редакция программного интерфейса, которая будет использована для scope - взаимодействия с языковой моделью: 'GIGACНAT API PERS' (для физических лиц) или ♦ I GIGACНAT _ API _CORP I (для юридических лиц); название применяемой разновидности языковой модели: 'GigaChat-2' model - (начального уровня), 'GigaChat-2-Pro' (среднего уровня) или 'GigaChat-2-Max' (высшего уровня). Чем выше уровень языковой модели, тем она «умнее», но и тем дольше «думает» над ответом 1; ♦ verify _ ssl _ serts - если True, перед соединением с языковой моделью будет проверена действительность установленных если F'alse - сертификатов НУЦ Минцифры, не будет. Автор советует давать этому параметру значение False, поскольку с проверкой сертификатов часто возникают проблемы; ♦ если True, языковая модель получит возможность выдавать отве­ streaming - ты не целиком, а частями, если в разд. ♦ только целиком (подробности False - - 22.3.5); temperature - температура языковой модели. Те:ипература большой языковой модели - настройка большой языковой модели, влияющая на креативность выдаваемых ею ответов. Чем выше температура, тем более «творческими» будут ответы. Однако при слишком высоком значении тем­ пературы языковая модель может начать галлюцинировать. Гал.1юцинации большой языковой модели - ответы, не имеющие связи с реально­ стью. Возникают при слишком высоком значении температуры. Температура задается в виде вещественного числа от о. о до 1. о. Значение None задает температуру по умолчанию, которая у каждой языковой модели своя; ♦ max _ tokens Токен - предельно допустимое количество токенов в ответе. минимальная единица текста, с которой работает большая языковая мо­ дель. В среднем на одно слово русского языка приходится во английского языка - 3/4 2 токена, а на одно сло­ токена. Значение None задает неограниченное количество токенов в ответе. 22.3.2. ИИ-клиент: отправка запросов и получение ответов Дпя отправки запроса языковой модели, с которой ранее было установлено соеди­ нение, служит метод invoke () объекта языковой модели: iП\'Oke (<текст отправляемого 1 вопроса>) И тем дороже в испо,1h·ювании (пол,робности ~ в разд. 22.5).
Часть 386 11/. Практическое Руthоп-программирование Метод возвращает объект класса AIMessage из модуля langchain__core.messages, представляющий полученный ответ. Этот объект, помимо самого текста ответа, содержит ряд служебных сведений о нем. Класс AIMessage предоставляет следующие полезные атрибуты и методы: ♦ ♦ text ( J - метод, возвращает строку с ответом языковой модели; атрибут, хранит служебные сведения об ответе в виде сло­ response_metadata варя Python. Элемент token _ usage этого словаря хранит вложенный словарь с размерами за­ проса и ответа, выраженными в токенах. Отдельные значения можно получить из следующих элементов вложенного словаря: • prompt _ tokens - • completion_ tokens - • total _ tokens - размер отправленного запроса; размер полученного ответа; совокупный размер вопроса и ответа. В качестве примера напишем простейший консольный ИИ-клиент, который лишь принимает от пользователя вопросы, отправляет их языковой модели и выводит полученные ответы. Выход из программы будет производиться после ввода слова «exit» (листинг 22.1 ). from langchain_gigachat import GigaChat model = GigaChat( # Подставьте сюда ваш ключ credentials=' . . verify_ssl certs=False авторизации while True: question = inрut('Введите вопрос: ') if question == 'exit': break response = model.invoke(question) print(response.text()) Код ИИ-клиента очень прост и не требует пояснений. Проверим его в работе: Введите вопрос: Большая языковая модель это Что тахое большая язьпс:овая модель? (БЯМ, от англ. **large language model**, класс алгоритмов машинного обучения, сетях, . . . предназначеннь~ Остальной для обработки вывод пропуmен основанных на естественного ... Замечательно! Наш первый ИИ-клиент работает! глубоких языка (НЛП) . LLМ) - нейроннь~
Урок 387 Искусственный интеллект 22. Однако выданный им ответ чересчур длинный. Попросим сократить его: Сократи предыдущий ответ до вопрос: Введите Я - GigaChat, мужчина, тексты, решать Работаю с задачи, текстом и говорю на 15 слов. русском как носитель. и преподавать дополнительными с помогать если навыками, Могу разными анализировать вопросами. доступно. Языковая модель ответила нам совсем не в тему ... Но почему? Потому что наш клиент не сохраняет историю взаимодействия с языковой мо­ делью - заданные ранее вопросы пользователем и ранее выданные ему ответы. Вследствие чего языковая модель не «помнит», о чем пользователь спрашивал ранее. 22.3.3. ИИ-клиент: хранение истории взаимодействия с языковой моделью В самом простом случае историю гии LangChain Python. Каждая предыдущие вопросы и ответы, в терминоло­ - называемые сообщенuя.,1wu, - можно хранить в обычном списке позиция такого списка должна быть представлена в виде объекта одного из следующих классов, объявленных в модуле langchain_core.messages: ♦ SystemМessage - своего рода вводная для языковой модели, сообщающая, как ей себя вести с пользователем; ♦ HurnanМessage - ♦ AIMessage - вопрос пользователя; ответ языковой модели. Именно в виде объекта этого класса воз­ вращает результат метод invoke () объекта языковой модели (см. разд. 22.3.2). Формат вызова конструкторов всех этих классов одинаков: <класс сообщенпя>(<текст вводной, вопроса Используя для хранения истории список или ответа>) Python, мы можем применять для добавле­ ния в него новых позиций знакомые нам методы класса list (см. разд. 5.1.2.2). В вызове метода invoke ( ) объекта языковой модели в качестве параметра сле­ дует указывать всю историю сообщений - чтобы модель «знала», о чем шел раз­ говор. У совершенствуем наш простой ИИ-клиент, «научив» его хранить историю сообще­ ний (листинг 22.2). from langchain_core.messages i.mport model GigaChat( SystemМessage, HШDaIJМessage
388 Часть 111. Практическое Руthоп-программирование messages = [ SystemМessage ( ''l'ъ1 мой личный :консультант. - ' 'Оrвечай на сообщения предельно понятным язы::ком. while True: question = inрut('Введите if question == 'exit': break: вопрос: ') ') messages.append(HшnanМessage(question)) response = model.invoke(messages) messages.append(response) print(response.text()) Мы создаем список Python с единственным элементом - вводной для языковой модели, представленной объектом класса SystemМessage. Перед отправкой получен­ ного от пользователя вопроса мы преобразуем его в объект класса HurnanМessage, добавляем в этот список, который и отправляем модели. После получения ответа от языковой модели (как мы помним, он представлен объектом класса AIMessage) мы также добавляем его в упомянутый список. Посмотрим, что выдаст нам исправленный клиент: Введите вопрос: Большая языковая модель умеет понимать делает . . . Что такое большая язы:ковая модель? и Остальной Большая языковая модель данным из Введите интеллект, речь или 15 слов. текст так который же, как это это тоже генерировать . . . Сократи предыдущий ответ до Интернета вопрос: GigaChat он человеческую вывод пропушен вопрос: что это мощный искусственный человек. Введите по - генерировать и это ИИ, других умеющий понимать и генерировать Является ли большой язы:ковой моделью GigaChat? одна из реализаций больших языковых моделей. является тексты, искусственным интеллектом, но текст источников. специально разработан Это значит, способным понимать и настроен и для русскоязычной аудитории. Ура! Мы только что обуздали могучий искусственный интеллект от «Сбера»! 22.3.4. ИИ-клиент: очистка истории Историю взаимодействия с языковой моделью желательно делать небольшой. Во­ первых, она занимает оперативную память, а большая история - много памяти. Во-вторых, после получения в качестве вопроса слишком большой истории у язы­ ковой модели могут начаться галлюцинации, и она выдаст нам нечто несусветное. Можно написать код, очищающий историю от ненужных сообщений, самостоя­ тельно, а можно использовать для langchain_core.messages.utils: этого функцию trim_ messages () из модуля
Урок 22. 389 Искусственный интеллект trim_messages(messages=<пocлeдoвaтeльнocть, хранящая I1сторию>, max _ tоkеns=<предельньп:~ размер истории в токенах>, [, on=None] [, start [, strategy=' last'] [, allow_partial=False] end_on=None] [, include system=False]) token _ соuntеr=<используемая языковая модель> Предельный размер истор11н должен быть задан в токенах языковой модели, указан­ ной в параметре token _ cour1ter. Необязательные параметры: ♦ стратегия удаления сообщений из истории в виде одной из строк: strategy - 'first' (будут сохраняться наиболее ранние сообщения, а более поздние - уда­ ляться) или 'last' (наоборот); ♦ allow _partials - если True, в процессе сокращения размера истории в ней мо­ гут появляться «обрезанные» сообщения, если False - такого допускаться не будет; ♦ тип сообщения, с которого должна начинаться очищенная история, start _ on - в виде строки: 'system' (вводная), 'human' (вопрос)или 'ai' (ответ).Можноука­ зать последовательность таких типов. Принимается во внимание, если в пара­ метре ♦ strategy end_on - указано значение 'last '; тип сообщения, которым должна заканчиваться очищенная история. Указывается так же, как и тип в параметре start _ on. Можно указать последова­ тельность типов. В следующем примере указывается, что история после очистки должна начи­ наться с вводной или вопроса, а заканчиваться ответом: from langchain core.messages.utils import trim_messages messages = trim_messages(messages=messages, strategy='last', start on=('system', 'human'), end_on='ai') ♦ include _ system - если True, вводная, находящаяся в начале истории, будет со­ хранена, если False в параметре strategy может не сохраниться. Принимается во внимание, если указано значение 'last'. Функция trim_ messages 1) возвращает ссылку на «очищенную» историю. Добавим в наш ИИ-клиент функциональность очистки истории. Пусть ее размер 500 токенов, сохраняются наиболее поздние сообщения, появление «обре­ занных» сообщений не допускается, сохраняется вводная, а очищенная история начинается с вопроса и заканчивается ответом (листинг 22.3). составит from langchain_core.messages.utils import tri.m_messages messages =
Часть 390 111. Практическое Руthоп-программирование def tri.mmer () : gloЬal messages messages = trim_messages(messages=messages, max_tokens=SOO, token_ counte~l, strategy=' last' , start_on='human', end_on='ai', include_system=Тrue) while True: question = inрut('Введите вопрос: ') print(response.text()) tri.mmer() Мы объявили функцию trimmer ( J, которая чистит историю, вызывая описанную ранее функцию trim_ messages () с необходимыми параметрами и присваивая воз­ вращенный ею результат той же переменной messages. И вызываем эту функцию после вывода каждого ответа. Таким образом, каждый раз новая, очищенная, исто­ рия заменяет старую, неочищенную. 22.3.5. Получение ответа по частям Можно предписать языковой модели выдачу ответа не целиком, а по частям - чанкам. Если ответ предполагается очень большим, выдача его чанками позволит существенно ускорить его вывод. Прежде всего при создании объекта языковой модели необходимо дать параметру streaming конструктора значение True ( см. разд. 22. 3.1). Так мы предоставим язы­ ковой модели возможность выдавать ответы чанками. Далее вместо метода invoke () ( см. разд. 22. 3.1) следует вызвать метод stream (), имеющий такой же формат вызова. Он возвращает итератор, который выдает последовательность объектов класса AIМessageChunk из модуля langchain_ core. messages, представляющих отдельные чанки ответа. Класс поддерживает метод text (), возвращающий текстовую часть ответа. В очередной раз переделаем наш ИИ-клиент, реализовав в нем получение ответов чанками (листинг 22.4). from langchain_core.messages :inport model = GigaChat( streaming=Тrue SystemМassage, HumanМessage, AIМessage
Урок 22. 391 Искусственный интеллект while True: rnessages.append(HurnanMessage(question}} response_ text = 1 1 # # # # # # # for chunk in model.stream(messages): chunk_ text = chunk. text () print(chunk_text, end=' ') response_text += chunk_text print() messages. append (AIМessage (response_ text)) 1 2 3 4 5 6 7 trirnrner 1} Хотя получение ответа чанками и ускоряет вывод его на экран, код, выводящий ответ, стал сложнее. Мы создаем переменную для хранения полного текста ответа (поз. 1в листинге позже преобразуем его в объект ответа и добавим в ис­ 22.4) - торию. Далее отправляем языковой модели историю, инициируем получение ответа чанками и перебираем полученные чанки в цикле (поз. каем текстовое содержимое (поз. 3), 2). Из каждого чанка извле­ выводим его в консоли без завершающего символа (чтобы фрагменты текста из чанков выводились вплотную, без разрывов между ними ответа (поз. 5). поз. и добавляем к постепенно формируемому полному тексту 4) После вывода последнего чанка ставим пустую строку, чтобы отде­ лить ответ от следующего вопроса (поз. 6). И наконец, преобразуем текст ответа в объект класса AIMessage и добавляем его в историю (поз. 7). Если мы запустим программу и введем в ней вопрос, то сразу увидим, что ответ выводится по частям. 22.3.6. Конкурентная работа с языковыми моделями Библиотека LangChain также предоставляет конкурентные инструменты для взаи­ модействия с языковыми моделями. Они реализованы следующими сопрограмма­ ми-методами класса модели: ♦ - ainvoke 1} фьючер (см.разд. с аналог метода invoke (} (см. разд. ответом, к которому можно 22.3.2). применить Возвращает обычный оператор приостановки 14.1.1): frorn asyncio irnport run async def f 1) : response = await rnodel.ainvoke('Kaкoй город является 'Тринидада print(response.text()} run (f ()) # Вьrnод (часть пропущена): # Столицей Тринидада и Тобаго ♦ astrearn() - столицей и Тобаго?') является город **Порт-оф-Спейн** аналог метода strearn() (см.разд. 22.3.5). ... Возвращает асинхронный итератор, обрабатываемый в цикле перебора с приостановкой (см.разд. 14.1. 2. 1):
Часть 392 async def f () : chunks = model.astream('Moжнo ли 'Еревана ///. Практическое Руthоп-программирование добраться в самолетом из ' Подгорицу?') async for chunk in chunks: print(chunk.text()) print () run (f ()) # Вывод (часть пропущена): # Прямого авиасообщения между Ереваном . . . и Подгорицей . . . нет. # Однако существует несколько вариантов маршрутов через пересадочные # пункты . . . 22.4. Разработка ИИ-агентов 22.4.1. Объявление инструментов Инструмент ИИ-агента программное средство, - которое позволяет ИИ-агенту выполнять действия, выходящие за рамки обычного обращения к большой языковой модели (выполнять поиск в Интернете и др.). Инструмент можно объявить в виде: ♦ функции - принимающей в качестве параметров необходимые для работы зна­ чения (например, искомое слово для интернет-поиска). Функция, реализующая инструмент, обязательно должна быть, во-первых, деко­ рирована декоратором tool () из модуля langchain_ core. tools, содержать строку документирования (см.разд. 15.2) а во-вторых, с описанием этого инстру­ мента. Упомянутое описание позволяет ИИ-агенту понять, что делает этот инст­ румент. Функция-инструмент должна возвращать результат своей работы, которым мо­ гут быть, например, сведения, найденные в Интернете. Часто у параметров функции-инструмента и возвращаемого ею результата ука­ зываются аннотации типов (см.разд. 15.1). Пример: from langchain_core import tool @tool def search(keyword: str) -> str: '' 'Поиск в Интернете по заданному искомому слову''' return search result ♦ объекта какого-либо класса, производного от класса ваsетооl из модуля langchain_core.tools.base. Этот объект должен содержать метод invoke (), который выполняет действия, предусмотренные разработчиком класса (например, ищет в Интернете строку, переданную с параметром).
Урок 22. 393 Искусственный интеллект 22.4.1.1. Инструмент для поиска в Интернете средствами DuckDuckGo В состав поставки тернет-поиска LangChain посредством входит в том числе инструмент для выполнения ин­ популярной поисковой службы DuckDuckGo'. Она известна тем, что не отслеживает активность своих клиентов, сохраняя их конфи­ денuиальность, а для работы с ней не требуются никакие ключи авторизации. Для работы упомянутый инструмент требует наличия дополнительных библиотек ddgs и duckdнckgo-search. У становить версии 9.6. * и 8.1. * этих библиотек соответ­ ственно, использованные автором при написании книги, можно подачей команд: pip install "ddgs==9.6" pip install "duckduckgo-search==8.1" инструмент представляется классом DuckDuckGoSearchResul ts из модуля langchain_coшnuni ty. tools. Конструктор этого класса вызывается в формате: Сам DuckDuckGoSearchResults([description=None] [, ] [max results=4]) Параметры: ♦ строковое описание инструмента, предназначенное для ИИ-аген­ description - та. Если задать None, будет использовано стандартное описание на английском языке; предельное количество результатов поиска, выдаваемых инстру- ♦ max _ resul ts ментом. Как и в случае любого другого инструмента, который реализован классом, произ­ водным от класса ваsетооl, для запуска поиска необходимо вызвать у созданного объекта этого инструмента метод invoke () ( см. разд. 22. 4.1), передав ему искомое слово или фразу. Проверим работу инструмента DuckDuckGoSearchResults в интерактивной оболочке: >>> from langchain_community.tools import DuckDuckGoSearchResults >>> tool = DuckDuckGoSearchResults(max results=l) >>> tool. invoke ( 'GigaChat') 'snippet: GigaChat - сервис на основе искусственного интеллекта, способный с общаться пользователями в режиме диалога, генерировать .. . . . . Часть вывода пропущена . . . Попробуйте несколько запросов., title: GigaChat - Telegram, link: https://t.me/s/official_gigachat' по запросу 22.4.2. тексты и изображения Создание ИИ-агента Библиотека LangChain включает средства для создания агентов, но они чрезвычай­ но неудобны в применении. Лучше использовать аналогичные инструменты, пред- 1 https://duckduck~o.com/.
Часть 394 лагаемые дополнительной библиотекой LangChain 111. Практическое Руthоп-программирование LangGraph. и радикально упрощает создание агентов Установить версию 0.6.* библиотеки LangGraph, Она является надстройкой над - как простых, так и сложных. описываемую в книге, можно по­ дачей команды: pip install "langgraph==0.6" Создание ИИ-агента выполняется вызовом функции create_react_agent ()' из мо­ дуля langgraph. prebuil t: create rеасt_аgеnt(<большая языковая модель>, <инструменты>[, prompt=None] [, checkpointer=None] [, pre_model_hook=None] [, post_model_hook=None]) Большая языковая модель, с которой будет работать агент, задается в виде ссылки на соответствующий объект. Инструменты следует указать в виде последовательности ссылок на функции и объекты, реализующие эти инструменты. Остальные пара­ метры: ♦ prompt - вводная для языковой модели в виде строки или объекта класса SystemМessage (см. разд. 22.3.3). Во вводной следует явно указать, какие инструменты доступны языковой моде­ ли и какие задачи они выполняют; ♦ checkpointer в разд. 22.4.5); ♦ pre _model _ hook - ссылка на объект хранилища истории (подробности - ссылка на функцию, которая будет вызываться перед отправ- кой языковой модели очередного вопроса; ♦ post _model _ hook - ссылка на функцию, которая будет вызываться после полу­ чения от языковой модели ответа на очередной вопрос. Написание этих функций рассмотрено в разд. 22.4.6. Функция возвращает объект готового к работе ИИ-агента. 22.4.3. ИИ-агент: отправка запросов и получение ответов Для отправки вопроса и получения ответа служит метод invoke () объекта ИИ­ агента: invоkе(<отправлямый вопрос>[, config=None]) Отправляемый вопрос указывается в виде словаря, единственный элемент которого хранит список также из единственного элемента - messages самого отправляемого вопроса, представленного объектом класса HumanМessage. 1 Фрагмент «react» в имени этой функции является сокращением от «REasoning and ACTiпg» (рассужде1111е и действие»). Собственно, ИИ-агенты тем и занимаются, что рассуждают и действуют.
Урок 22. Искусстеенный интеллект 395 Пар!метр config задает дополнительные настройки текущего сеанса «общения» пользователя с языковой моделью . Его значение указывается в виде словаря. Ис­ пользоаание этого параметра описано в разд. Метод 22.4.5. invo ke () возвращает результат также в виде словаря . Его элемент me ssage s содержит список ответов, выданных языковой моделью и представленных объек­ тами класса AIMe ssa ge . Эти ответы включают в себя как промежуточные данные (например, результаты поиска, полученные от поисковой службы), так и оконча­ тельное резюме, сгенерированное на их основе языковой моделью и хранящееся в последнем элементе упомянутого списка . Чтобы закреnить полученные знания, напишем простого ИИ-агента, который будет получать от пользователя запрос на поиск данных в Интернете, искать их посредст­ вом поисковой службы DuckDuckGo и выдавать пользователю ответ, сгенериро­ ванный на основе результатов поиска (листинг Л•-r который выдает ответ, осноаанный на результатах поиска т• вlitм fr om from fr om f r om 22. . МИ-агент, 22.5). langchain_g i gachat i mport GigaChat lsngchain_commun i ty . tools import DuckDuckGoSearchResults langgraph.pre bui lt i mpo r t create_ reac t_agent langch•in_ core.messages i mport HumanМ e ss a ge mode l = Gi ga Chat ( # П од с тав ь те сюда ключ а в торизации ваш credent ia l s=' . . ve rif y_s s l_certs=False sea r che r = DuckDuckGoSearchResults( d e scri p tion= ' Пoиcк в Интерн ете с помощью службы # 1 # 2 # 3 # # 4 5 DuckDuckGo ', max r e s u l ts =2 prompt ' Ты - умный помощник. ' Используй 'Выдавай agent searcher только ' +\ дn я краткое поиска в Инт ернете. ' +\ резюме. ' crea te_ rea c t _a gent (mode l , (sea r che r,), p r ompt=p r ompt ) while True: question = inрut( ' Введите в о прос : ' ) if question == ' exit ' : brea k re spons e = agent . invo ke({ ' messages ' : f or message i n respons e[ ' me ssages ' ] : p ri nt () p r in ~( message.tex t()) [ Huma nМes s age( que st i on )] })
396 Часть 111. Практическое Руthоп-программирование После подготовки языковой модели мы создаем инструмент для поиска средствами службы (поз. 1 DuckDuckGo, представленный объектом класса DuckDuckGoSearchResul ts 22.5). В параметре description конструктора задаем описание в листинге инструмента, предназначенное для языковой модели. Предельное количество выда­ ваемых результатов поиска устанавливаем равным руктора класса) 2 (параметр max results конст­ этого достаточно. Сам объект инструмента сохраняем в пере­ - менной searcher. Далее пишем вводную для языковой модели (поз. 2). В ней явно сообщаем, что языковая модель может использовать инструмент searcher для поиска в Интернете (как и было сказано в разд. 22.4.2). Создаем объект ИИ-агента на основе подготовленных ранее языковой модели, на­ бора инструментов и вводной (поз. create _ react _ agent () 3). Отметим, что во втором параметре функции всегда следует указывать последовательность инструментов, даже если инструмент всего один. Полученный от пользователя вопрос отправляем языковой модели в виде словаря описанной ранее структуры (поз. ся в нем ответы в цикле (поз. 4). Получив список ответов, выводим находящие­ 5). Запустим ИИ-агент, зададим ему какой-либо вопрос и посмотрим, что он выведет: Введите вопрос: Какая погода snippet: - Какая погода сейчас в городе Во.mкском Волгоградской области? сейчас в городе Волжском Волгоградской области? Погода на сегодня. Ощущается как 17. - Волжский • +18°. утром: +18°, ясно, • +l 7°. днем: +17°, малооблачно, Ощущается как 16. +17°, малооблачно, ... , title: Погода на сегодня - • +17°. вечером: - Волжский, link: https://yandex.ru/pogoda/ru/volzhskiy/details/gardeni ng/today, snippet: - Прогноз в Волжском сегодня (Волгоградская область Россия) . . . . Остальной текст ответа пропущен . . . Сейчас в Волжском Атмосферное +18° давление в днем, ясно. Ночью ожидается +18 .. 20°, ясно. норме. Видно, что набор ответов включает в себя сам вопрос, заданный пользователем (первая строка полученного вывода), результаты, полученные от поисковой службы (следующие строки), и резюме, сгенерированное языковой моделью (последние две строки). В результате чего вывод получился весьма объемистый. Нам же нужно лишь резюме. 22.4.4. Обработка ответов Список из элемента messages словаря, возвращенного методом invoke () ИИ-агента, содержит объекты трех разных классов:
Урок 397 22. Искусственный интеллект вопрос, заданный пользователем; ♦ HumanМessage - ♦ ToolMessage из модуля langchain_ core .messages - результаты, выданные каким- либо инструментом; ♦ ответ языковой модели. AIMessage - Мы можем выяснить род ответа, проверив его на принадлежность определенному классу. В этом нам поможет функция isinstance () (см. разд. Исправим написанный ранее ИИ-аrент (см. листинг лишь резюме (листинг --- 22.5) 7.4). так, чтобы он выводил 22.6). from langchain_core.messages import HumanМessage, AIМessage while True: for message in response['messages'): if isinstance(message, AIМessage): print(message.text()) Этот код выводит лишь ответы, являющиеся объектами класса AIMessage, - т. е. резюме языковой модели. Как говорится, доверяй - но проверяй. Запустим ИИ-аrент, зададим ему тот же вопрос и посмотрим ответ': Введите вопрос: ка~сая погода сейчас в городе Волжском Волгоградской области? На данный момент в Волжском туман, ощущается как +1°. температура воздуха без осадков, Ветер отсутствует. Давление составляет 753 мм рт. +3° ст. Как говорится, коротко и ясно. 22.4.5. ИИ-агент: хранение истории взаимодействия с языковой моделью Наш ИИ-аrент имеет существенный недостаток - он не хранит историю взаимо­ действия с языковой моделью и не помнит, о чем шла речь в предыдущих вопросах и ответах. Мы можем проверить это самостоятельно, запустив его и задав подряд вопросы «Какая сейчас погода в Волжском?» и, например, «А в Волгограде?» Ско­ рее всего, в ответе на второй вопрос речь будет идти отнюдь не о поrоде 2 ... В составе библиотеки LangGraph имеется класс MemorySaver, объявленный в модуле langgraph.checkpoint.memory. Он реализует хранение истории в оперативной памя- 1 За время. прошедшее между написанием разд. 22.4.3 и 22.4. 4, погода в родном городе автора изменилась. 2 Так. автору ИН-агент выдал перечень волгоградских достопримечательностей.
Часть 398 ///. Практическое Руthоп-прогрвммироаанив ти, что подходит в большинстве случаев. Конструктор этого класса вызываете.я без параметров. Готовый объект хранилища истории следует передать создаваемому ИИ-аrенту, присвоив ссылку на него параметру checkpointer функции create react agent 1) (см. разд. 22.4.2). Кроме того, сеансу общения пользователя с языковой моделью необходимо дать уникальный идентификатор, который должен вместе с каждый вопросом в вызове метода (см. разд. 22.4.3). передаваться языковой модели invoke (), в словаре, в паР3метре config Упомянутый словарь должен содержать единственный эщ:мент configuraЫe с вложенным словарем, элемент thread_id которого должен хранить созданный ранее идентификатор сеанса. В качестве такого идентификатора можно использовать любое произвольно сгене­ рированное значение (например, псевдослучайное число). Однако наиболее часто для этой цели применяется UUID, который можно получить вызовом функции uuid4 () из модуля uuid стандартной библиотеки. UUID (Universal Unique !Dentifier, строковый идентификатор, универсальный уникальный идентификатор)­ уникальность которого гарантирована на глобальном уровне. Снова усовершенствуем наш ИИ-аrент, дав ему «память» (листинг from langgraph.checkpoint.memory import from uuid import uuid4 22. 7). МemorySaver memory = МemorySaver () config = { 'confiquraЫe' : { 'thread_id' : uuid4 () } } agent = create_react_agent (model, (searcher, ) , prompt=prompt, checkpointer=memory) while True: question = inрut('Введите if question == 'exit': break response = вопрос: ') agent.invoke({'1118ssages': [HumanМessage(question)]}, config=config) Особых пояснений здесь не требуется, так что сразу перейдем к тестирсванию переделанного ИИ-аrента: Введите вопрос: Кахая сейчас погода в городе Волжском ВолгоградсJСОК об.паС'l'М? На данный момент в Волжском туман, ощущается как +1°. без осадков, Ветер отсутствует. температура воздуха Давление составляет 753 мм рт. +3° ст.
Урок 22. Введите 399 Искусственный интеллект вопрос: А в Волгограде? На данный момент в Волжском 'Гуман, ощущается как +1°. Сейчас в Волгограде малооблачно, ветер слабый, 5 без осадков, Ветер отсутствует. температура воздуха Давление составляет температура +13°С, 753 мм рт. +3 ° ст. ощущается как +12°С, м/с. Как видим, выводится ответ не только на последний вопрос, но и на все предыду­ щие. Нам же нужен лишь последний ответ. Исправить этот недостаток очень просто - достаточно выводить в консоль только последний элемент списка из элемента messages результата, возвращенного мето­ дом invoke : ! . Внесем в код И И-агента необходимые правки (листинг from langchain_core.messages import 22.8). HumanМessage while True: response = agent. invoke ( { 'messages': [HumanМessage (question) ] } , config=config) print(response['messages'] [-1).text()) for mcssage in response['messages']: if isinstance (message, P,H4essage): print(message.teJtt()) После чего наш ИИ-агент станет работать нормально. Можете проверить сами! 22.4.6. Работа с состоянием ИИ-агента Библиотека ИИ-агента - LangGraph предоставляет возможность получить доступ к состоянию в частности, к хранящейся в нем истории сообщений, с целью, на­ пример, очистить ее от «старья». Функция crea te _ react _ agent 1), создающая ИИ-агента, принимает параметры pre _ model_hook и post_model_hook (см.разд. 22.4.2). В них задаются ссылки на функции, которые будут вызваться соответственно перед отправкой очередного вопроса язы­ ковой модели и после гюлучения ответа на него. Каждой из этих функций в качест­ ве единственного параметра передается ссылка на объект состояния ИИ-агента: def hook(state): # Что-либо делаем с состоянием из параметра state agent = create react_agent( . . . , pre_model hook=hook) Объект состояния обладает функциональностью отображения (см.разд. элемент mes5a:Jes хранит последовательность сообщений: def hook(state): history = stale [ 'messages'] 10.2.6). Его
400 Часть /11. Практическое Руthоп-программирование for message in messages: Любые действия, изменяющие последовательность сообщений, следует произво­ дить с ее копией, которую можно получить, взяв срез исходной последовательно­ сти от первого до последнего элемента: def hook(state): # Получаем копию последовательности сообщений history_copy = history[:] # Изменяем полученную копию нужным образом В качестве результата обе упомянутые функции должны возвращать словарь, эле­ мент messages которого должен содержать последовательность сообщений, что будут добавлены к имеющимся в текущем состоянии. Если требуется удалить из истории какое-либо сообщение, следует вставить в по­ следовательность с новыми сообщениями объект класса PemoveMessage из модуля laпgchain _ core. messages. Конструктор этого класса вызывается в следующем фор­ мате: РеmоvеМеssаgе(id=<идентификатор удаляемого сообщения>) Идентификатор сообщения можно получить из атрибута id объекта сообщения. Пример удаления из истории сообщения, хранящегося в переменной message to delete: from langchain core.messages import PemoveMessage def hook(state): return { 'messages': [PemoveMessage(id=message_to_delete.id)] + modified_history Если необходимо удалить все сообщения, то параметру id конструктора класса PemoveMessage следует передать значение переменной PEMOVE _ ALL_ MESSAGES из моду­ ля langgraph.graph.message. Пример полной замены истории сообщений: from langgraph.graph.message import PEMOVE ALL МESSAGES def hook(state): return { 'messages': [PemoveMessage(id=REMOVE_ALL_MESSAGES)] 22.4.7. + full пew_history ИИ-агент: очистка истории Очистить историю сообщений в ИН-агенте можно также с помощью функции trim_ messages () ИЗ модуля 1angchain_ core .messages. uti1 s (см.разд. 22.3. -1). Вызов
Урок 22. 401 Искусственный интеллект этой функции можно поместить в функцию, заданную в параметре pre_ model _ hook ИЛИ post_model_hook функции create_react_agent () (см. разд. Добавим в наш ИИ-агент очистку истории (листинг 22.9. ИИ-а1 22.4.6). 22.9). IIJIIIIJ,I истории from langchain_core.messages.utils iшport trim_messages from langchain_core.messages iшport RemovaМessage from langgraph. graph .message iшport REМJVE_ ALL_мESSAGES def tr:iJDmer(state): new_ messages = trim_ messages (messages=state [ 'messages' ] , max_tokens=SOO, token_counter-,oodel, strategy='last', start_on='human', end_ on= 'ai' , include_ system=Тrue) return { 'messages ' : [RemovaМessage (id=REМJVE_ ALL_ МESSAGES) agent = create_ react_agent (model, ] + new_ messages (searcher, ) , prompt=prompt, checkpointe~ry, post_ model_ hook=triпloer) 22.5. Что еще нужно знать о работе с искусственным интеллектом ♦ При работе с GigaChat API расходуется внутренняя валюта, называемая токе­ нами (как и единица объема обрабатываемого текста). На обработку каждого токена вопроса и на генерирование каждого токена ответа тратится по токену внутренней валюты. Так, если мы отправим языковой модели вопрос объемом в 20 и получим ответ в 100 токенов, то израсходуем 120 имеющихся у нас валютных токенов. ♦ При выборе бесплатного тарифного плана Freemium пользователю выделяются следующие суммы во внутренней валюте: • для работы с разновидностью ня) • - GigaChat-2 языковой модели (начального уров­ 500 ООО токенов; GigaChat-2-Pro (средний уровень)- по 50 ООО токенов. для работы с уровень) и GigaChat-2-Max (высший При достаточно интенсивной работе этого количества токенов хватит на одну­ две недели (после чего токены придется покупать). ♦ Также доступны платные тарифные планы Business и Enterprise, при выборе ко­ торых выделяются большие суммы в валютных токенах (и снимаются некоторые другие ограничения).
402 Часть ♦ Библиотека langchain-community, 111. Практическое Руthоп-программирование служащая связкой между LangChain и кон­ кретными языковыми моделями, поддерживает многие программы такого рода: OpenAI, Anthropic, Google Gemini и многие другие. Но, к сожалению, не все - и тогда приходится прибегать к другим библиотекам 1 ... ♦ Библиотеки LangChain и LangGraph позволяют создавать достаточно сложные ИИ-приложения, способные обращаться к сторонним интернет-службам, базам данных, использовать для работы документы, хранящиеся на локальном диске, и др. Все возможности этих библиотек описаны в официальной документации. 22.6. Самостоятельное упражнение Попробуйте использовать для работы наиболее мощную языковую модель из се­ мейства GigaChat- GigaChat-2-Max. Только помните, что лимит на ее использова­ Freemium составляет лишь 50 ООО валютных токенов. ние по тарифному плану 1 GigaChat тому пример.
Заключение Вот и подошла к концу книга о замечательном языке программирования Python. Мы изучили сам язык, его наиболее востребованные инструменты и приемы про­ граммирования. Мы научились загружать данные из Интернета, выполнять парсинг веб-страниц, писать графические и веб-приложения, работать с базами данных, производить математические расчеты с массивами чисел, рисовать научные графи­ ки и даже успешно управляться с искусственным интеллектом. А еще мы написали несколько десятков программ, как чисто учебных, так и вполне способных приго­ диться на практике. Конечно, очень много осталось, так сказать, за кадром. Практически не описаны инструменты стандартной библиотеки для работы со значениями даты, времени, временнь1ми отметками, значениями временньrх промежутков (а в этом Python си­ лен!), файлами и папками, встроенные средства для шифрования и др. Поверхност­ но освещены последовательности байтов и множества Python. Совершенно не рас­ смотрены встроенные средства для работы с базами данных формата на данными по сети, рисования произвольной графики и SQLite, обме­ программирования графических приложений'. Python - исключительно мощный и развитый язык, позволяющий разрабатывать программы самого разного назначения. Ему посвящают толстенные книги, даже многотомники, описывающие его во всех тонкостях. Задача автора этой книги другая - рассказать об основах этого языка, о его наибо­ лее часто применяемых на практике инструментах и показать, какие замечательные вещи можно на нем делать. Дать начальный толчок к изучению Python, ту самую «печку», от которой следует начинать «плясать». А учить Python стоит. В истории информационных технологий есть много языков «мертвых», когда-то появившихся, какое-то время применявшихся на практике, возможно, даже пер­ спективных, но потом по ряду причин вышедших из употребления и к настоящему времени полностью забытых 2 ... Но Python - это один из тех языков, которые, скорее всего, никогда не умрут. Ведь на нем реализовано технологическое чудо XXI века - искусственный интеллект! Который явно пришел к нам надолго. 1 Да, 2 в стандартной библиотеке есть и такие инструменты! Правда, не очень удобные ... Навскидку можно вспомнить такие языки, как Algol, APL, Perl и Curl.
Заключение 404 И напоследок несколько интернет-ресурсов по теме книги: ♦ https://www.python.org/ - «домашний» сайт Python. Дистрибутивы, докумен­ тация, новости от команды разработчиков, ссылки на полезные ресурсы; ♦ https://pypi.org/ - репозиторий PyPI. Пригодится при поиске дополнительных библиотек и инструкций к ним; ♦ https://goo.su/apsUszF 1 - подборка полезных статей на русском языке; ♦ подборка разнообразных ресурсов: статей, видеоуро- https://goo.su/lwxvPnl - ков и курсов, в том числе бесплатных; ♦ https://goo.su/uNН8dYQ - еще одна подборка статей, весьма полезных. И, безусловно, вы, уважаемые читатели, всегда можете прибегнуть к помощи любимой поисковой службы и всезнающего искусственного интеллекта GigaChat. А автор прощается с вами. Успешного программирования и поменьше ошибок в коде!;) Владимир Дронов 1 Для вашего удобства эта и следующие длинные ссылки преобразованы в сокращенные с помощью сервиса bttps://goo.su.
ПРИЛОЖЕНИЯ :> Приложение 1. Установка исполняющей среды :> Приложение 2. Приоритет операторов :> Приложение 3. Использование ИИ для изучения Python и написания программного кода :> Приложение 4. Python Описание файлового архива Python

Приложение 1 Установка исполняющей среды Чтобы приложения, написанные на Python, Python успешно исполнялись, необходимо ус­ тановить на компьютер исполняющую среду этого языка. Это программный пакет, который перед выполнением преобразует Python-кoд в представление, «понятное» центральному процессору компьютера. Внимание/ Текущая версия Python поддерживается лишь Windows версий 8.1, 10 и 11. Для установки исполняющей среды мы выполним последовательность действий, приведенную далее. 1. Заходим на веб-страницу загрузок, входящую в состав «домашнего» сайта Python 1• 2. Looking for а specific release? (Ищете какую-либо конкретную щелкаем на rиперссылке Python 3.13.* (эта версия языка нем - Находим раздел версию?), а в являлась наиболее актуальной на момент подготовки книги, последняя цифра в номере версии может быть любой). Будет выполнен переход на страницу с описанием и дистрибутивами выбранной версии 3. Python. - щелкаем на гиперссылке Windows Installer (32-Ьit) (Установщик для Windows (32-разрядная редакция)) или Windows Installer (64-Ьit) (Установщик для Windows (64-разрядная редакция)), Находим раздел Files (Файлы), а в нем в зависимости от редакции операционной системы. Через некоторое время будет загружен файл с установщиком исполняющей среды. 4. Запускаем загруженный файл на исполнение - на экране откроется первое окно установщика (рис. П 1.1 ). 5. Устанавливаем флажок Add python.exe to Р А ТН (Добавить python.exe в список путей из переменной РАТН). Это позволит нам в дальнейшем запускать исполняющую среду, не указывая полный путь к ней, из любой папки файловой системы. 1 https://www.python.org/downloads/.
Приложение 408 ;1, Python З.13.4 (64-Ьit) Setup 1 х lnstall Python 3.13.4 (64 - Ьit) Select lnstall Now to install Python with defзult settings, or choose Customize to enable or disable features. ➔ tnstall Now C:\lkers\vt.d\Applnta\loal\Progr•ms\Python'J'ython313 lndudes IDlf. pip and do<umenutюn associations C,,at,s shortcuts and fil<! ➔ Cystomize installation Choos@ python ror windows location 1nd feotures О Use admin privijeges when installing ру.ехе 8 Add QYthon.exe to РАТН Рис. П1.1. Установщик исполняющей среды В составе исполняющей среды Cancel Python: 1 поставляется утилита ру.ехе, запуска Руthоn-файлов щелчком мыши. Флажок installing окно служащая для Use admin privileges when ру.ехе (Использовать административные права при установке ру.ехе), присутствующий в окне установщика, будучи установленным, предписывает сделать эту утилиту доступной всем пользователям, зарегистрированным в сис­ теме. Если вам это необходимо, установите упомянутый флажок, в противном случае можете оставить его сброшенным. 6. Нажимаем кнопку то второе окно Customize installation (Настроить установку)- будет откры­ установщика (рис. Пl .2), в котором мы укажем состав устанав­ ливаемой исполняющей среды. ;1, Python З.13.4 (64 -Ьit) Setup х Optional Features 8 Qocumentation lnstalls the Python documentat,on files. ■ Qip lnstalls ■ р,р, which can download and ,nstall other Python pad<ages. td/tk and !DLE lnstalls tkinter and th@ IDlE dev@lopment мvironment. ■ Python test suite lnslalls lhe standard library lesl suile. 8 ру /auncher О for ill users (requires admin privileges) lnstalls the gloЬal 'ру' l.iuncher to make it eas1er to start Python. python windows (О( Рис. П1.2. Установщик исполняющей среды Next Python: окно 2
Установка исполняющей среды 7. 409 Python Оставляем установленными все флажки, присутствующие в этом окне, и нажи­ маем кнопку Next (Следующее) . Следующее, третье окно установщика позволит задать дополнительные пара­ метры (рис. П\.3). х с Advanced Options 8 8 8 8 8 lnstall Python 3.13 for а.II users Associate jiles with Python (requires the 'ру' laundler) Create shortcuts for installl!d apprк:ations дdd Python to i:nvironment variabll!s frecompile standard fiЬrary О Download dl!Ьugging »m1Ьols О Download deЬug Ьinaril!S (requires VS 2017 or later) О Download fn!e-threadl!d Ьinaril!S (l!Xpl!limental) Customize install location Browse C:\Program Files\Python313 python fc windows Васk Рис. П1.3. Установщик исполняющей среды 8. Qincel Jnstall Python, окно 3 Associate files with Python (requires the 'ру' launcher) (Ассоциировать Руthоn-файлы с утилитой ру. ехе), Create shortcuts for installed applications (Создать ярлыки для установленных приложений 1 ), Add Python to environment variaЫes (Добавить Python в переменные окружения) и устанавливаем флажок Precompile standard library (Предварительно откомпи­ Оставляем установленными флажки лировать стандартную библиотеку). Ассоциирование Руthоn-файлов с утилитой ру . ехе позволит запускать эти файлы щелчками на них. Добавление пути к исполняющей среде Python в список путей из переменной РАТН даст возможность запускать исполняющую среду, набрав команду python в консоли 2 . Предварительная компиляция стандартной библио­ теки ускорит запуск Руthоn-программ. Флажок Install Python 3.13 for all users (Установить для всех пользователей), будучи поставленным, установит исполняющую среду в папку или Program Files Program Files (х86) (в зависимости от редакции дистрибутива), тем самым сделав исполняющую среду доступной всем пользователям, зарегистрированным в сис­ теме. Если же оставить этот флажок сброшенным, исполняющая среда будет 1 Имеются в виду приложения, поставляемые в составе исполняющей среды. 2 Консоль - это утилита, которая предназначена для выполнения команд, набираемых на клавиатуре, Windows поставляются три консоли: Windows 11 ). Использовать можно любую из них . и просмотра результатов их выполнения. В составе PowerShell и Terrninal (начиная с командная строка,
Приложение 410 1 установлена в папку профиля текущего пользователя и станет доступной лишь ему. Какой подход выбрать, на взгляд автора, дело вкуса 1 • 9. Нажимаем кнопку ние UAC, Install (Установить). Если на экране появится предупрежде­ отвечаем на него положительно. Установка исполняющей среды займет какое-то время. По ее окончании будет выведено последнее, четвертое окно установщика. 10. Нажимаем кнопку Close (Закрыть) последнего окна, чтобы завершить установку. Проверить, установлена ли на компьютере исполняющая среда Python, можно, за­ пустив консоль и подав в ней следующую команду, не забыв завершить ее нажа­ тием клавиши <Enter>2 : python Она запустит исполняющую среду, которая выведет ряд сведений о себе, вклю­ чающих номер версии (подчеркнут): Python 3.13.4 (tags/v3.13.4:8a526ec, Jun 3 2025, 17:46:04) [MSC v.1943 64 bit (АМD64)] on win32 Туре "help", "copyright", "credi ts" or "license" for more information. Для завершения исполняющей среды следует подать в ней команду exi t, exi t () или просто закрыть консоль. 1 Сам 2 автор предпочитает первый подход. В дальнейшем автор более не будет напоминать об этом.
Приложение Приоритет операторов Таблица П1 .1. Приоритет операторов Приоритет Операторы [а, Создание списка [i for i on seq] 13 (а, {kl: ь, а, Списковое включение Создание кортежа с) k2: Python в порядке уменьшения Фрагмент выражения, взятый в круглые скобки с] ь, Python Описание ) ( Ь) {k: v for i in seq) {i for i in seq) Создание словаря Словарное включение Включение во множество Обращение к элементу последовательности x[indexlkey] или отображения 12 Взятие среза последовательности x[start:end] Обращение к атрибуту объекта obj.attribute funclmethod(a, 11 await 10 ** Ь) Вызов функции или метода Оператор приостановки Возведение в степень *,/,//,% 9 Умножение, деление, деление нацело, взятие остатка от деления Сложение, вычитание 8 +, - 7 in, not in, is, is not, <, <=, >. >=, ! == =1 Вхождение, невхождение, идентичность, неидентичность, операторы сравнения 6 not Логическое НЕ 5 and Логическое И 4 or з Логическое ИЛИ else if 2 lamЬda 1 .- 2 Сокращенный формат выражения ветвления Объявление лямбда-функции Присваивание в составе выражения
Приложение 3 Использование ИИ для изучения Python и написания программного кода Для изучения Python и написания на нем программного кода можно использовать уже знакомую нам языковую модель GigaChat - через ее общедоступный веб­ интерфейс 1 . Достаточно ввести интересующий вопрос в области редактирования, находящейся в центре страницы, и нажать клавишу Через некоторое время GigaChat <Enter> (рис. ПЗ .1 ). выдаст ответ в виде отформатированного текста, содержащего описание, формат записи выражения и примеры кода (рис. ПЗ.2). Оr­ вет будет выводиться частями в процессе его прокрутки вниз. 8В9 Что такое списковое включение Python? о @ Прикре-пить 00 0 Giga Рис. ПЗ.1. Вопрос, заданный 1 https://giga.chat/. GigaChat
Использование ИИ для изучения Сnисковое включение Python и написания программного кода (List comprehension) в 413 это компактный способ создания списка Python - путем преобразования элементов другого итерабельно,·о объекта (например, списка, кортежа, строки) . Оно позволяет одновременно создавать новый список и применять к элементам исходного контейнера некоторые условия или трансформации . Структура спискового включения Общий синтаксис выглядит следующим образом: [expression for item in iteraЫe if condition] Где: переменная цикла, представляющая каждый элемент последовательности . о item - о itегаЫе о condition - объект, элементы которого обрабатываются циклом. (необязательно) - условие, которое фильтрует элементы перед добавлением в итоговый список . о expn,ssion - выражение, определяющее преобразование элемента. Примеры Простое списочное включение Создаем список квадратов чисел от О до 4: squares = fi**2 for i in ranqe(S}l @ Прмкрспкть Рис. ПЗ.2. Ответ, выданный GigaChat Под ответом может находиться набор сопутствующих и уточняющих вопросов, организованных в виде кнопок (рис. ПЗ.3) . Щелчок на такой кнопке выведет ответ на этот вопрос . Вы можете задать произвольный уточняющий вопрос или вопрос по совершенно другой теме, введя его в область редактирования Спросите GigaChat, распола­ гающуюся в самом низу страницы, под ответом (эта область редактирования видна на рис. ПЗ . 2 и ПЗ.3), и также нажав клавишу <Enter>, - и получить ответ на него. Можно задавать вопросы формата: «Объясни, что делает следующий код: <фраг­ мент программного кода>» - и GigaChat выдаст исчерпывающее объяснение. Только следует помнить, что нажатие клавиши <Enter> немедленно отправляет введенный вопрос языковой модели для обработки . Поэтому, чтобы начать вводить текст с новой строки, требуется нажать комбинацию клавиш <Shift>+<Enter>. Можно отправить языковой модели файл с программой и попросить переделать ее согласно заданным требованиям. Сначала следует прикрепить требуемый файл к отправляемому вопросу, для чего достаточно нажать кнопку Прикреп·ить, нахо­ дящуюся под областью для ввода текста вопроса (эта кнопка видна на рис. ПЗ.2), выбрать в появившемся меню пункт Файл и указать нужный файл в выведенном на
Приложение 414 3 экране стандартном диалоговом окне открытия файла'. После чего можно написать сам вопрос с требованиями к программе и отправить его нажатием клавиши <Enter>. Таким образом, списковое включение является мощным инструменто м Python, позволяющим эффективно создавать списки в одном выражении. :• ... Доработать текст • В чем разница между списковым включением и обычным циклом for? Можно ли использовать несколько условий фильтрации в списковом включении? Какие преимущества дает использование списковых включений? С, •1 t @ Прикрепить О Рис. ПЗ.З. Набор сопутствующих и уточняющих вопросов Так, автор отправил GigaChat модуль с программой из листинга 2.1 и попросил переписать ее, добавив обработку ошибок преобразования введенного значения в число (на тот случай, если пользователь при вводе допустит ошибку). Языковая модель в ответ вьщала код, приведенный в листинге ПЗ .1. 1 Листинг ПЗ.1. Программа из листинга 2.1, переделанная языковой моделью try: # Запра=ваем ввод у пользователя input_value # = преобразуемую величину в дюймах: строку в вещественное ') число float(input_value) Вычисляем сантиметры crns # inрut('Введите Пробуем преобразовать inches # = = inches * 2.54 Выводим результат рrint(f'Результат: 1 Предварительно {crns:.2fl см.') необходимо прочитать появившееся на экране соглашение об обработке персональных данных и согласиться с ним. Для чего следует установить флажок, подтверждающий согласие, и нажать кнопку. Эту операцию достаточно выполнить лишь один раз - при прикреплении самого первого файла.
Использование ИИ для изучения except ValueError: # Обрабатываем рrint("Ошибка: Python и написания программного кода значение невозможн о преобраз о вать ошибку, если введено некорректное числовое 415 в чи сл о значение.") Как видим, переделанный код включает комментарии с краткими пояснениями того, что делает тот или иной его фрагмент. Также в составе ответа, под кодом, языковая модель вывела краткое резюме, сообщающее, как поведет себя программа в случае ошибки ввода. И наконец, можно попросить языковую модель написать какую-либо программу (по крайней мере, простую), как можно подробнее описав, что эта программа должна делать. Языковая модель выдаст готовый код программы, объяснит, как она работает, и покажет пример ее вывода. Вам, уважаемые читатели, останется лишь сохранить выданный код в файле и проверить его работу. Так, на вопрос автора «Напиши программу на целых чисел от 1 до 100 Python, которая выводит квадраты в одну строку» языковая модель выдала код, показанный в листинге ПЗ.2. Листинг ПЗ.2. Программа дпя вывода квадратов целых чисел, написанная языковой моделью squares = [str(i**2) for i in range(l, 101)] resul t = ' ' . j oin ( squares) print(result) Программа полностью функциональна и достаточно компактна. Если же она вас, уважаемые читатели, почему-либо не удовлетворяет, вы можете попросить GigaChat переделать ее согласно заданным вами требованиям . Например, автор попросил переработать программу так, чтобы она занимала две строки, и получил код из лис­ тинга ПЗ.3. Листинг ПЗ.3. Программа из листинга ПЗ.2 после сокращения, выпоnненноrо язь1ковой моделью squares = [str(i**2 ) for i in range(l, 101)] print (' '. join (squares)) Вполне возможно писать таким образом и достаточно сложные программы. Важно лишь помнить три важных момента. Во-первых, потребуется предельно подробно описать то, что программа должна делать, с учетом всех нюансов и нештатных си­ туаций, которые могут возникнуть при ее работе, - а это может оказаться доволь­ но сложной зада.чей. Во-вторых, в особо сложных случаях языковая модель может начать галлюцинировать и выдаст код, который просто не запустится (поскольку использует несуществующие программные инструменты'). И в-третьих, если тре­ буется написать нечто крайне специфическое, языковая модель может вообще от­ казаться выдавать код, сославшись на то, что у нее нет необходимых знаний для выполнения столь сложной задачи 2 . автора была пара тахих случаев . И тогда придется писать программу самостоятельно ... 1У 2
Приложение 4 Описание файлового архива Файловый архив, сопровождающий «БХВ» по интернет-адресу: книгу, размещен на сервере https://zip.bhv.ru/9785977521260.zip. издательства Ссьmка на него доступна и со страницы книги на сайте https://Ьhv.ru/. Список папок и файлов, имеющихся в архиве (вложенность папок и файлов показа­ на отступами): ♦ <номер урока> - • !sources - папка с материалами урока с указанным номером. Состав папки: папка с исходными материалами, необходимыми для выполнения упражнений в текущем уроке; • Leaming 0 0 папка с листингами из текущего урока. Ее состав: <номер листинга> - файл с кодом из листинга с указанным номером; <номер листинга 1>-<номер листинга 2> код из всех листингов, начиная с номера • Yourself - папка с файлами, содержащими 1и заканчивая номером 2; папка с результатами выполнения самостоятельных упражнений. Ее состав аналогичен составу папки Leaming, только находящиеся в ней фай­ лы и папки имеют порядковые номера, соответствующие нений; ♦ readme.txt - текстовый файл с описанием файлового архива. номерам упраж­
Предметный указатель _add_() 163 _ aenter_() 221 _aexit_() 221 _aiter_() 220 all 145 _anext_() 220 _bool_() 161 _bytes_() 161 _call_() 168 __ceil_() 161 class 134, 141 _contains_() 166 _del_() 129 _delattr_() 170 _delitem_() 166 dict 169 239 - doc_enter_() 171 _eq_() 164 _exit_() 171 file- 149 _tloat_() 161 _tloor_() 161 _tloordiv_() 163 _ge_() 164 _getattr_() 169 _getattribute_() 170 _getitem_() 166 _gt_() 164 _hash_() 161 _iadd_() 163 _itloordiv_() 163 _imod_() 163 _imul_() 163 _init_() 128 _int_() 161 _ipow_() 163 _isub_() 163 _iter_() 164 _itruediv_() 163 _le_() 164 _len_() 165 _lt_() 164 _mod_() 163 module 123 _mul_() 163 name_ 123, 142, 149 _пе_() 164 _neg_() 163 _next_() 164 __pow_() 163 _qualname_ 123, 142 _radd_() 163 _reversed_() 165 _rtloordiv_() 163 _rmod_() 163 _rmul_() 163 _round_() 161 _rpow_() 163 rsub_() 163 _ rtruediv_() 163 _setattr_() 169 _setitem_() 166 slots 138 _str_() 161 _sub_() 163 _truediv_() 163 _trunc_() 161 А abs() 65 acos() 65 acquire() 206 add() 353 add_ argument() 196 add_media_files() 293 add_static_files() 292 A/Message 386, 387 A/MessageChunk 390 ainvoke() 391 aiofiles 226
418 aiohttp 266 align_items 358 al\() 312 and 50 Any 233 AnyStr 232 Арр 343 append() 84 ArgumentError 196, 199 ArgumentParser 195 ArgumentTypeError 197, 199 array() 378 as 143, 144, 147, 151, 155 asin() 65 astream() 391 async 217, 220, 221 AsyncGenerator 236 Asynclterator 236 atan() 65 AttributeError 35, 126 attrs 257 Audio 282 audio() 282 auto() 173 await 218 AwaitaЫe 235 Axes 372 в back() 296 background_color 357 bar() 373 BaseException 150 BaseTool 392 Beautiful Soup 253 BeautifulSoup 253 BiglntField 301 BinaryField 302 Ьоо\50 BooleanField 301 Вох 353 break 58 browser 329 Button 321, 346 button() 321 bytearray 80 bytes 79 Bytes!O 228 Предметный указатель с Саl\аЬ\е 235 cancel() 225 CancelledError 225 case 55 cbrt() 65 ceil() 66 CharField 301 charset 267 Checkbox 323 checkbox() 322 children 255 choice() 87 class 127, 130, 133 classes() 292 classmethod() 135 clear() 85, 98 ClientResponse 267 ClientSession 267 close() 213, 288, 360 close_ connections() 3 14 closed 360 Code 283 code() 283 Col 284 col() 284 color 357 compile() 177, 182 ConfirmDialog 363 ConnectionError 250 content 251,268,282, 327, 359 contents 255, 327 contextmanager() 170 continue 58 cos() 65 cpu_ count() 215 create() 306 create_react_ agent() 394 create_task() 222, 224 css.select() 262 current tab 355 D daemon 205 data 353 dataclass() 241, 243 DateField 302 DatetimeField 301 ddgs 393
Предметный указатель DecimalField 302 deepcopy() 103 def 108 defaultdict 1О 1 defaultdict() 1О 1 degrees() 65 del 36, 84 delete() 307 DeprecatioпWarning 259 dialog() 363 dict 95 direction 357 disaЫe() 288 DoesNotExist 307 DOTALL 183 duckduckgo-search 393 DuckDuckGoSearchResults 393 Dunder-мeтoд 160 Е е 66 earliest() 320 element() 290 elif 51 else51,53, 72,151,220 Empty 210 empty() 210 еnаЫе() 288 enaЫed 288, 344, 355 encoding 251 Enum 172 enumerate() 94 ErrorDialog 364 except 151 Exception 150 exclude() 309 exists() 312, 313 exit() 61, 344 exitcode 213 ехр() 66 ехр2() 66 expand() 187 Expansion 286 expansion() 286 extend() 84 F factorial() 66 False 49 field() 244 419 Figure 372 file() 297 filter() 115, 308 finally 151 find() 74, 259 find _ all_next() 260 find _ all_previous() 261 find _ next() 260 find_next_siЫing() 260 find _ next_siЫings() 260 find _parent() 261 find _parents() 261 find _previous() 261 find_previous_siЫing() 260 find _previous_siЫings() 260 findall() 186 finditer() 186 first() 311 flex 358 float 62 float() 63 FloatField 301 floor() 66 focus() 345 font_family 357 font size 357 font_style 357 font_weight 357 footer 289 footer() 289 for 71, 88, 95,220 force _reload() 280 ForeignKeyField 302 forward() 296 freeze _ support() 214 from 143, 147 from _ url() 297 frozenset 105 Full 210 full() 21 О fullmatch() 184 function 114 G gap 357 gather() 223 general 329 generate_schemas() 305 Generator 236 GeneratorExit 150 get() 97,210,249,252,267,307
Предметный указатель 420 get_annotations() 23 7 get_nowait() 21 О get_or_ create() 319 get_or_ none() 319 GigaChat 383, 384 GigaChat API 383 • GIL 204 global 110 global_ enum() 176 Grid 286 grid() 285, 377 н hash() 161 header 289 header() 289 headers 251,267 height 356 help() 240 hex() 80 hide() 289, 360 hist() 374 horizontal_position 355 html() 291 HТТPError 250 HumanMessage 387 icon 347 id 400 IDLE Shell 19 if51, 53, 55, 88, 95 IGNORECASE 183 image 347 Image 280 image() 280 ImageView 347 import 143, 144, 147 ImportError 35 in 71, 74, 88, 95, 220 index 355 index() 86 IndexError 35 InfoDialog 363 init() 304 Input 322 input() 40, 321 insert() 84 int 62 int() 63 IntEnum 174 IntField 30 l invoke() 385, 394 is 102 is not 102 is_ alive() 205 isinstance() 60, 141 items 351 items() 98 iter_ chunked() 268 iter_ content() 252 lterator 236 J join() 85,205, 211 JoinaЫeQueue 213 json() 251,268 JSONField 302 justify_content 357 к KeyError 35 keys() 98 L label 322 Label280,345 label() 280 LangChain 384 langchain-community 384 langchain-gigachat 384 LangGraph 394 last() 311 latest() 320 left drawer 289 left_ drawer() 289 legend() 377 len() 71 limit() 312 Link 281 link() 280 list 82 ListSource 349 Literal 233 LiteralString 233 Lock 206, 213 locked() 207 log() 66 logl0() 66 lower() 73
421 Предметный указатель м main_loop() 344 main_window 358 MainWindow 358 Mapping 235 margin 356 margin_bottom 357 margin_left 357 margin_right 357 margin_top 357 Markdown 281, 282 markdown() 281 match 55 Match 185 match() 184 Matplotlib 366 max() 65, 85, 115 max_ horizontal_position 356 max_ vertical_position 356 MemorySaver 397 Meta 303 MiltiUploadEventArguments 327 min() 65, 85, 114 MISSING 244 Model 300 МUL ТILINE 183 MultilineTextlnput 345 MultipleObjectsReturned 307 N name 175, 205, 257, 327 namedtuple() 90 NameError 35 names 327 Namespace 198 Never 233 next_siЫing 255 next_siЫings 256 NiceGUI 272 None 36 NoneType 36 nonlocal 118 NoReturn 233 not 50 not in 74 NotlmplementedError 166 Number 325 number() 325 Numberlnput 347 NumPy 378 о o._abs_() 163 offset() 312 ok 251,267 оп() 292 оп_shutdown 3 15 оп_startup 315 open() 228, 288 OptionContainer 354 Optionltem 355 or 51 order_ Ьу() 3 11 out of limits 325 р page() 273 page_title() 297 parent 255 parents 255 parse_ args() 198 pass 54 Passwordlnput 348 Pattern 177, 184 pause() 283 pbkdf2_hmac() 329 pi66 pie() 375 Pillow 191 pip 189 pk 308 play() 283 plot() 373 рор() 84, 98 popitem() 98 position 358 previous_siЫing 256 previous_siЫings 256 print() 40, 41 Process 213 property() 136 purge() 188 put() 209 put_nowait() 21 О PyPI 189 Q qsize() 210 QuerySet 308 QuestionDialog 363 Queue 209,226
422 R radians() 65 Radio 323 radio() 323 raise 153 randint() 67 random() 66 range 94 range() 93 read() 268 reason 251, 267 RecursionError 124 reduce() 116 RegexFlag 182 release() 207 REMOVE_ALL_MESSAGES 400 RemoveMessage 400 replace() 74 RequestException 250 requests 249 reset() 327 Response 251 response_metadata 386 result() 225 return 108 reversed() 104 ReverseRelation 313 rfind() 74 right_drawer 289 right_drawer() 289 round() 65 Row 284 row() 284 rsplit() 73 run() 218,219,279 run_method() 327 Runner 219 s sample() 87 sanitize() 325 save() 306 scroll_to_bottom() 346,353 scroll_to_row() 353 scroll_to_top() 346, 353 ScrollContainer 355 search() 184 seed() 67 seek() 283 Select 325 Предметный указатель select() 323 selection 352 Selection 351 Self233 self_and_parents 255 Separator 285 separator() 2 85 Sequence 234 set 104 set_options() 323, 325 set_title() 377 set_xlabel() 377 set_ylabel() 3 77 setdefault() 98 show() 289, 360, 377 SimpleQueue 209, 213 sin() 65 sleep() 218 slice 166 Slider 326, 348 slider() 326 SmalllntField 301 sort() 115 source 280, 283 split() 73, 187 splitlines() 73 sqrt() 65 start 94 start() 205 startup() 358 state 360 staticmethod() 135 status 267 status_ code 251 step 94 stop 94 StopAsynclteration 220 Stoplteration 164 str 68 stream() 390 StrEnum 174 strftime() 219 string 256 strings 257 strip() 73 stripped_strings 257 style 345, 356 Style 356 style() 292 sub() 186 subplots() 372 sum() 85 Switch 346
Предметный указатель SyntaxError 35 SystemMessage 387 т TabError 46 ТаЫе 352 tan() 65 Task 222 task_ done() 21 О TaskGroup 224 tau 66 text 251,280,288, 345-347, 355 text() 268, 386, 390 text_align 357 Т extarea 3 22 textarea() 322 TextField 301 Textlnput 345 Thread 204 tick value 349 TimeDeltaField 302 TimeField 302 Timeout 250 title 360 title() 73 to() 296 Toga 339 toggle() 289 tool() 392 ToolMessage 397 tooltip() 297 trim_messages() 388 True 49 trunc() 66 try 151 tuple 89 type 140, 327 type() 38 TypeError 35 types 327 423 u UnboundLocalError 110 uniform() 66 Upload 328 upload() 326 UploadEventArguments 327 upper() 72 URL-napaмeтp 276 user 329 UUID 398 uuid4() 398 UUIDField 302 V value 175, 288, 322, 323, 325, 326, 345, 346, 348,349,351 values() 98, 3 14 vertical_position 356 Video 283 video() 283 visiЬility 358 visiЫe 280 w width 356 Window 360 WindowState 360 with 155 у yield 159 z ZeroDivisionError 151
Предметный указатель 424 Группа А Аккордеон 287 Аннотация типа 231 Атрибут 32, 125, 349 ◊ добавленный 138 ◊ закрытый 135 ◊ классовый 126, 134 ◊ объектный 126 ◊ открытый 135 ◊ именованная ◊ незахватывающая ◊ нумерованная 182 182 181 Датакласс 241 Декоратор ◊ класса ◊ функции 140 120, 134 Делетер 137 Деструктор 129 Диапазон 93 298 Байт-код48 Блок задач д Б База данных, реляционная 181 224 ◊ 53 Блокировка 206 Большая языковая модель з 383 Задача222 в Вариант Замыкание Запись 179 Ввод40 и Включение ◊ во множество ◊ словарное ◊ списковое 105 383 383 Импорт 31, 143 Индекс 27, 79, 298 ◊ ключевой 299 ◊ компаундный 299 ◊ составной 299 ◊ уникальный 301 Инструмент 392 ИИ-агент 100 88 ИИ-клиент Возбуждение исключения Временная отметка 150 301 Вывод40 ◊ 118 298 множественный Вызов функции 26, 93 23, 106 Выполнение ◊ асинхронное ◊ конкурентное 217 217 ◊ параллельное 203 Выражение 19 ◊ ветвления 51, 53 ◊ выбора 55 ◊ управляющее 49 ◊ условное 51 Интерактивная оболочка ◊ консольная Интерактивный режим 19 Интерпретатор Исключение 19 150 Искусственный интеллект Исполняющая среда 36 104, 164 ◊ асинхронный 220 Итерация цикла 56 История Итератор г 385 93, 104 ◊ выражение 95 ◊ метод 98 ◊ функция 93 Геттер 136 Галлюцинации Генератор Глобальная блокировка интерпретатора 19 37 к Квантификатор 179 32, 125 базовый 130 вложенный 139 Класс ◊ 204 ◊ 19 383
Предметный указатель ◊ декорируемый ◊ производный 425 о 140 130 ◊ родительский 139 Ключ 29, 95, 298 Ключевое слово 31 ◊ исключения Код41 ◊ контекста Обработка исключения Командный ключ ◊ обязательный 151 Обработчик 194 195 0 ◊ 151 154 с приостановкой события 221 277 Комментарий Обратная ссылка Компиляция Обращение к переменной 45 48 Конкатенация 72 Консоль 409 Конструктор 128 Контейнер 340 Контекст 154 ◊ ◊ глубокая ◊ поверхностная 103 ◊ класса ◊ функции 21 Оператор 21 90 ◊ л Логическая величина 49 116 м Маршрут273 ◊ возврата ◊ идентичности ◊ комбинированного присваивания ◊ логический 74 ◊ ◊ неидентичности ◊ приостановки присваивания 0 ◊ сравнения ◊ удаления ◊ со значением Наследование ◊ 308 130 множественное 133 59 54 Менеджер контекста Набор записей 102 218 24 в составе выражения пустой Метасимвол н 102 50 невхождения 74 ◊ 155 178 Метод 32, 125 ◊ закрытый 135 ◊ классовый 126, 135 ◊ общедоступный 135 ◊ объектный 126 ◊ статический 135 Множество 104 ◊ неизменяемое 105 Модель 299,349 Модификатор 309 Модуль 31, 143 ◊ инициализации 146 ◊ стартовый 143 159 вхождения 276 378 108 с приостановкой ◊ ◊ параметризованный Массив 41 Операнд 0 Лямбда-функция 127 108 Окно файловое 103 29, 89 именованный 32, 33, 125 генераторный 104 текущий 33 Объявление ◊ Кортеж 24 Объект Копия ◊ 181 49 84 Оптимизация 43 Опция 195 Отображение Очередь 195 IО 1 209 Ошибка синтаксическая 34 п Пакет 146 ◊ вложенный ◊ родительский 146 146 Параметр 23, 106 ◊ двоякий 107 ◊ именованный 41, 106 ◊ необязательный 23, 11 О ◊ ПОЗИЦИОННЫЙ 36, 106 ◊ строго именованный 107 ◊ строго ПОЗИЦИОННЫЙ 106 64
Предметный указатель 426 Парсер 253 249 с Парсинг Перегрузка операторов 162 Свойство Перекрытие метода 131 Переменная 24, 25, 26 ◊ глобальная 109 ◊ локальная 108 136 Связь одна со многими Сеттер 299 136 Символ специальный 69 Словарь 178 Подстрока 46 Поиск 298 Поле 298 29, 95 Событие 277 Соль 329 Сообщение 387 Сопрограмма 217 ◊ генератор 222 Список 28, 82 Спойлер 286 Срез 27, 71 Ссылка 102 ◊ автоинкрементное Стандартная библиотека ◊ внешнего ключа Стек вызовов Переопределение метода Перечисление ◊ глобальное Подкласс 131 172 176 130 Подключение 144 Поднабор 298 299 ◊ индексированное 298 ◊ ключевое 298 Пользователь текущий 333 Последовательность 78 ◊ изменяемая 79 ◊ неизменяемая 79 ◊ непронумерованная 79 ◊ пронумерованная 79 Последовательность байтов Страница Строка 272 27, 68, 78 ◊ документирования ◊ форматируемая Суперкласс Схема 130 т 79 Поток Таблица ◊ вторичная ◊ обслуживаемая ◊ первичная 203, 228 203 ◊ служба 205 Приглашение 19 Приложение 40 главный 21 24 ◊ в составе выражения ◊ множественное ◊ позиционное ◊ 299 299 299 ◊ функции ◊ цикла 108 56 Температура 385 26, 33, 38, 60 ◊ обобщенный 237 Токен 385,401 59 26, 93 Тип данных 91 40 консольная 298 Тело Приоритет оператора Программа 238 75 299 ◊ Присваивание 31 122 44 Программный код 41 у Процесс ◊ 213 главный 213 Условие Пустой тип данных Путь ◊ 36 147 шаблонный 273 р выполняющееся ◊ истинное ◊ ложное ◊ не выполняющееся Распаковка 91 Регулярное выражение 124 177 28 50 50 50 Утверждение Размер последовательности Рекурсия 50 ◊ 50 180 ф Фабрика функций Файл статический 119 292, 293
Предметный указатель 427 Фильтрация ч 298 23, 106 ◊ вложенная 11 7 ◊ встроенная 3 1 ◊ декорируемая 120 ◊ родительская 117 Фьючер 218 Функция Чанк х Хеш 161 56 бесконечный ◊ перебора 0 вещественное ◊ с плавающей точкой ◊ целое Элемент ◊ ◊ ◊ 21, 26 26 э ц Цикл 252 Число 57 71, 94 с приостановкой с условием 56 220 28, 78, 172 21
Прохоренок Н. А., Дронов В. А. Python 3. Самое www.bhv.ru необходимое. 2-е изд. Отдел оптовых поставок: e-mail: opt@bhv.ru Быстро и легко осваиваем самый стильный Python - язык программирования • Основы языка • Утилита • Работа с файлами и каталогами • Доступ к данным • Pillow и Python 3 pip Wand: SQLite и MySQL работа с графикой • Получение данных из Интернета • Библиотека • Разработка оконных приложений • Параллельное программирование • Потоки • Примеры и советы из практики Tkinter Описан базовый синтаксис языка данных, операторы, условия, Python 3: циклы, типы регулярные выражения, встроенные функции, объектно-ориентированное программирование, обработка исключений, часто используемые модули стандартной библиотеки и ус­ тановка дополнительных модулей. Даны основы к базам данных SQLite и MySQL, SQLite, описан интерфейс доступа ODBC. Рассмотрена ра­ Wand, получение данных из в том числе посредством бота с изображениями с помощью библиотек Pillow и Интернета и использование архивов различных форматов. Python - 3.6.4, добавлены описания JSON, библиотеки Tkinter и разработки Во втором издании описана актуальная версия утилиты pip, работы с данными в формате оконных приложений с ее помощью, реализации параллельного программирования и использования потоков для выполнения программного кода. Прохоренок Николай Анатольевич, профессиональный программист, имеющий большой практический опыт создания и продвижения динамических сайтов с использованием JavaScript, РНР, Perl и MySQL. Автор книг «HTML, JavaScript, РНР и MySQL. HTML, Джентльмен­ ский набор WеЬ-мастера» и др . Дронов Владимир Александрович , профессиональный программист, писатель и журналист, 1987 года . Автор более 20 популярных компьютерных книг, в том «HTML, JavaScript, РНР и MySQL. Джентльменский набор WеЬ-мастера», «Python 3 PyQt 5. Разработка приложений» и др . Его статьи публикуются в журналах «Мир ПК» «ИнтерФейс» (Израиль) и на интернет-порталах IZ City и TheVista.ru. работает с компьютерами с числе и и
ДобрякП. Python. www.bhv.ru Красивые задачи для начинающих Отдел оптовых поставок: e-mail: opt@bhv.ru Python - это красиво! Ценность книги состоит в том, что в ней приводится детальное пошаговое описание процесса написания программы для решения каждой поставленной за­ дачи. • Структурное программирование • Динамическое программирование • Объектно-ориентированное программиро­ вание • Функциональное программирование. Главная причина популярности языка Python - его лаконичность, простота и выразительность. Один и тот же алгоритм на этом языке можно запрограм­ мировать по-разному - так, что будет казаться, будто программа написана на разных языках. В этой книге приводятся алгоритмы решения задач на которые автор считает красивыми Python, - все они имеют не­ сколько вариантов решений и формир)'J()Т алгоритмическое мышление. Эти алгорит­ мы - часть хорошего 1Т -образования. Рассмотрев предложенные задачи, знакомый с основами Python читатель сможет проверить, действительно ли он умеет программи­ ровать, а изучив их решения, значительно улучшит свои алгоритмические навыки. Павел Вадимович Добряк - кандидат технических наук, преподаватель Уральского феде­ рального университета. Проводит занятия по различным языкам программирования, базам данных, искусственному интеллекту и проектированию информационных систем. Репетитор по математике и информатике.
Фаррелл Д. Python. Как стать профессионалом www.bhv.ru Отдел оптовых поставок: e-mail: opt@bhv.ru Программисту-новичку не терпится увидеть, как его код заработает. В свою очередь, разработчик­ профессионал обязан писать софт, который бьm бы максимально надежен. Хороший код должен рабо­ тать быстро, легко масштабироваться, не доставлять проблем с поддержкой. Наконец, код должен быть безопасен, качественно спроектирован и докумен­ тирован, его должно быть легко обновлять и вер­ сионировать. Эта книга поможет вам пройти путь от начинающего Руthоn-программиста до уверенного Руthоn-разработчика. Книга помогает понять, почему популярный язык в мире Python - самый необыкновенно хорош - для профессиональной разработки. Работая с этим языком, легко приобрести важные профессиональ­ ные навыки - научиться функции и классы, продумывать и писать качественные именовать API, переменные, пользоваться объекта­ ми. Также в ней объяснено, как справляться с неизбежными отказами, в особенности сетевыми, соблюдать ключевые правила обеспечения безопасности, подключаться к базам данных и научиться профессионально решать конкретные задачи в рамках больших проектов на Python. Значительная часть книги посвящена Руthоn-фреймворку коряющему серверную веб-разработку на Python, Flask, упрощающему и ус­ поддерживающему создание стати­ ческих веб-страниц и способствующему интеграции серверной и клиентской частей веб-приложения. Дуг Фаррелл - опытный веб-разработчик, занимающийся программированием с и специализирующийся на Руthоn-разработке. 1983 года
Азиф М. Python для гиков www.bhv.ru Отдел оптовых поставок: e-mail: opt@bhv.ru Вы изучите: • Принципы разработки и управления сложными проектами 00 PYTHON мн • • • • (TDD) Многопоточность и многопроцессорность в Python Написание приложений с использованием кластера Apache Spark для обработки больших данных Разработку и развертывание бессерверных программ в облаке на примере • гиков Способы автоматизации тестирования приложений и разработки через тестирование • Google Cloud Platfoпn (GCP) Создание на Python неб-приложений и REST API, использование среды Flask Использование Python для извлечения данных с сетевых устройств и систем управления сетью Соэдаеайrе эффективные приложен.т , используя лучшие практики проrраммирования (NMS) Packt> (/jw- • Применение Python для анализа данных и машинного обучения Книга подробно рассказывает о разработке, развертывании и поддержке крупномас­ штабных проектов на Python. Представлены такие концепции, как итераторы, генера­ торы, обработка ошибок и исключений, обработка файлов и ведение журналов. При­ ведены способы автоматизации тестирования приложений и разработки через тестирование (TDD). Рассказано о написании приложений с использованием класте­ ра Apache Spark для обработки больших данных, о разработке и развертывании бес­ серверных программ в облаке на примере Google Cloud Platfoпn (GCP), о создании веб-приложений и REST API, использовании среды Flask. Показаны способы приме­ нения языка для создания, обучения и оценки моделей машинного обучения, а также их развертывания в облаке, описаны приемы использования данных с сетевых устройств и систем управления сетью Python (NMS). для извлечения Мухаммад Азиф, программный архитектор, обладающий обширным опытом в области веб­ разработки, автоматизации сетей и облаков, виртуализации и машинного обучения. Возглав­ лял многие крупномасштабные проекты в различных коммерческих компаниях. В 2012 году получил степень доктора философии в области компьютерных систем в Карлтонском универ­ ситете (Оттава, Канада) и в настоящее время работает в компании специалиста. Nokia в качестве ведущего
ИНТЕРНЕТ-МАГАЗИН BHV.RU Интернет-....,.аэмн ИЩТСА.КТN «5XI• • Более 30 N?:r на 1-)ОСсийскс»,1 рынке по электронике и робототехнике по издате.льскwл • Книги и наборы КН ИГИ ,РОБОТЫ , ЭЛЕКТРОНИКА 5D-38rpalla bhv .ru/elements Электронные компоненты ~ для мейкеров :+ .,.., ,,~