Текст
                    о 1 G О
О О
о О
j 1 о Q
----------L
ИзучаемРуЬКоп
Заметки репетитора
Оглавление
Предисловие..................................................5
1	Введение в Python..........................................6
1.1	Языки программирования.................................6
1.2	О языке Python........................................11
1.3	Установка Python и первая программа...................16
1.4	Редактор кода Visual Studio Code......................22
1.5	Рекомендации по оформлению кода.......................29
1.6	Комментарии...........................................33
2	Основы Python.............................................37
2.1	Переменные............................................37
2.2	Типы данных...........................................44
2.3	Числа.................................................54
2.4	Арифметические операции...............................63
2.5	Математические функции................................68
2.6	Основы работы со строками.............................74
2.7	Экранирование и кодирование символов..................80
2.8	Ввод и вывод данных...................................88
2.9	Логические выражения и операторы......................92
2.10	Ветвление...........................................103
2.11	Циклы...............................................112
2.12	Ошибки и исключения.................................123
3	Коллекции................................................135
3.1	Введение в коллекции.................................135
3.2	Индексация и срезы.................................. 141
3.3	Перебор элементов коллекций..........................149
3.4	Общие функции и операторы коллекций..................156
3.5	Методы преобразования и выравнивания строк...........168
Предисловие 2
3.6	Методы поиска и проверки строк.......................181
3.7	Списки и кортежи.....................................192
3.8	Методы списков.......................................201
3.9	Множества............................................211
3.10	Операции над множествами............................220
3.11	Словари.............................................230
3.12	Генераторы коллекций................................246
4	Функции..................................................255
4.1	Создание функций.....................................255
4.2	Параметры и аргументы функций........................268
4.3	Области видимости переменных.........................279
4.4	Рекурсивные функции..................................286
4.5	Функции высшего порядка..............................293
4.6	Анонимные функции....................................299
4.7	Итераторы и генераторы...............................305
4.8	Замыкание и декораторы...............................314
5	Объектно-ориентированное программирование................325
5.1	Классы и объекты.....................................325
5.2	Инкапсуляция и ограничение доступа...................338
5.3	Наследование и полиморфизм...........................347
5.4	Получение данных об объекте..........................361
5.5	Атрибуты и методы класса. Статические методы.........372
5.6	Перегрузка арифметических операторов и операторов сравнения 381
5.7	Перегрузка операторов контейнера.....................397
6	Модули и пакеты..........................................412
6.1	Введение в модули и пакеты...........................412
6.2	Модуль math для математических вычислений............422
6.3	Модуль random для генерации случайных чисел..........435
Предисловие 3
6.4	Модуль datetime для работы с датой и временем.........443
6.5	Установка сторонних пакетов. Виртуальное окружение....458
7	Работа с файлами..........................................469
7.1	Чтение и запись файлов................................469
7.2	Работа с CSV-файлами..................................483
7.3	Работа с JSON-файлами.................................495
7.4	Сохранение и восстановление	объектов..................507
7.5	Взаимодействие с файловой системой....................516
Послесловие.................................................526
Ответы и решения............................................527
Предисловие 4
Предисловие
Если вы читаете эти строки, значит, вы решили попробовать себя в программировании - и это отличный выбор! Неважно, сколько вам лет, есть ли у вас опыт или вы только начинаете - эта книга создавалась для всех, кто хочет научиться программировать на Python.
Меня зовут Хаустова Ирина, я репетитор по математике и информатике, а также энтузиаст программирования на Python. Уже почти шесть лет я занимаюсь индивидуальной подготовкой школьников к ЕГЭ и олимпиадам, а в течение одного года я работала учителем в школе. Эта книга выросла из моих конспектов и заметок, которые формировались за годы преподавания и проверены на занятиях с учениками разных возрастов и уровней подготовки.
Python часто выбирают в качестве первого языка программирования. Он хорошо подходит как для обучения и подготовки к ЕГЭ, так и для решения реальных практических задач. В последние годы особенно активно развиваются нейронные сети и искусственный интеллект, и Python стал основным языком для работы в этой области. Поэтому изучение этого языка помогает не только успешно справляться с учебными задачами, но и понимать технологии, которые формируют настоящее и будущее.
Для того, чтобы уверенно разрабатывать программы (для учёбы, работы или собственных проектов) важно понимать, как устроен сам язык программирования: как работают переменные, функции и классы. Именно эти базовые, но фундаментальные знания вы можете найти на страницах этой книги. Помимо теории каждый параграф содержит примеры кода с пояснениями и задания для самопроверки, помогающие закрепить материал на практике.
Я очень надеюсь, что этот учебник станет для вас понятным и надёжным помощником в изучении Python, а процесс обучения будет не только полезным, но и интересным. Спасибо, что выбрали эту книгу и решили пройти этот путь.
Желаю вам вдохновения, терпения и удовольствия от программирования! И помните, путь в тысячу вёрст начинается с первого шага.
С уважением, Хаустова Ирина
Ваш репетитор
Предисловие 5
Глава 1
Введение в Python
1.1 ЯЗЫКИ ПРОГРАММИРОВАНИЯ
В 21 веке программы стали настолько привычной частью нашей жизни, что порой мы даже не обращаем на них внимания. Вспомните браузер, через который вы просматриваете любимые сайты, или антивирус, защищающий ваше устрой-ство. Все эти инструменты являются результатом работы программистов - людей, занимающихся npoi раммированисм. то есть созданием программ. Любая про!рамма представляет собой последовательность команд, которые должен выполнить компьютер для решения поставленной задачи. При этом такие команды записываются на специальных языках - языках npoi раммирования
Подобно тому, как люди общаются па разных языках, например, русском или английском, программисты используют разные языки для «разговора» с компьютером. Каждый язык программирования обладает своими особенностями, сильными сторонами и областями применения. Выбор подходящего инструмента зависит от решаемой задачи, предпочтений разработчика и особенностей проекта. Например, для разработки мобильного приложения на Android обычно выбирают язык Kotlin. а на iOS - Swift.
Некоторые языки со временем становятся более популярными, другие -менее. Так. один из самых известных рейтингов, индекс TIOBE определяет популярность языков npoiраммирования на основе анализа поисковых запросов, связанных с ними в поисковых системах. Хотя этот рейтинг не является абсолютным показателем. Python уже несколько лет подряд уверенно занимает лидирующие позиции в этом рейтинге, обгоняя таких «ветеранов» как C++, Java и С#.
Языки программирования как иностранные языки
Представьте, что вам нужно сказать фразу «Привет, мир!» на разных иностранных языках. На английском вы скажете: «Hello world!», на французском: «Bonjour le monde!», а на китайском: «ШЭДР#?!».
Введение в Python 6
Каждый язык уникален и имеет свои правила построения слов и предложений. Подобная ситуация наблюдается и в мире языков программирования: несмотря на одинаковую суть задачи, её реализация на каждом языке выглядит совершенно иначе.
Давайте взглянем на простой пример - вывод на экран фразы «Привет, мир!». Вот как это делается на языке JavaScript:
console.1ор("Привет, мир!’);
А вот тот же самый результат на Python:
print( Привет, мир!')
Хотя обе программы решают одну и ту же задачу, синтаксически они существенно различаются.
Функция print() в Python
Python, как и любой другой язык, предлагает множество готовых команд, которые называются функциями. Эти функции позволяют нам выполнять различные действия, например, выводить текст на экран или производить математические расчеты.
Функции в Python записываются строчными буквами и сопровождаются парой круглых скобок. Внутри этих скобок указываются параметры - данные, которые мы передаем функции для её работы (если они необходимы).
Для вывода текста на экран в Python используется функция print(). Хотя сами языки программирования отличаются друг от друга, ключевые слова и понятия в них основаны на английском языке. Например, слово «print» переводится как «печатать».
Также языки программирования имеют строгие правила записи команд синтаксис. Любая, даже незначительная ошибка в написании может привести к тому, что программа не будет работать. Так, после слова print должны идти круглые скобки, а выводимый текст (строка) заключён в двойные (") или одинарные (') кавычки.
Компьклер и двоичный код
Возникает закономерный вопрос: «Неужели компьютер понимает английский язык?». Конечно же, нет. Языки программирования созданы для удобства нас, программистов. На самом деле компьютер «говорит» на своем собственном Введение в Python 7
языке - машинном коде, который представляет собой последовательность нулей и единиц.
Для центрального процессора компьютера 0 и 1 - это всего лишь электрические сигналы: отсутствие тока и его наличие, как выключатель света в положении «выкл.» и «вкл.». Любая информация, будь то буква, число или команда, должна быть представлена в виде такой последовательности нулей и единиц.
Например, фраза «Привет, мир!» для компьютера может быть закодирована следующим образом:
11010000 10011111 11010001 10000И00 11010000 10111000 11010000 10110010 11010000 10110101 11010001 10000010 00101100 00100000 11010000 10111100 11010000 10111000 11010001 10000000 00100001
Однако данный набор нулей и единиц - это лишь закодированный текст (данные), а компьютер должен еще и знать, что ему с этими данными делать. Для этого используется машинный код, который представляет собой набор инструкций (команд), понятных непосредственно центральному процессору. Эти команды также представлены в виде последовательности нулей и единиц, и они указывают процессору, какие конкретные операции нужно выполнить: сложить числа, переместить данные из одной ячейки памяти в другую или сравнить значения.
Низкоуровневые и высокоуровневые языки программирования
В зависимости от того, насколько язык программирования близок к машинному коду, их разделяют на низкоуровневые и высокоуровневые.
Низкоуровневые ягыки, такие как ассемблер, предоставляют прямой доступ к ресурсам компьютера-процессору и оперативной памяти. Они используются для обеспечения максимальной скорости исполнения программы и полного контроля над оборудованием, например, в написании драйверов устройств, операционных систем и программ для микроконтроллеров.
Однако ассемблер довольно сложен н изучении и использовании, а его команды зависят от конкретной архитектуры процессора. Каждая команда соответствует одной операции процессора, что делает программу максимально эффективной, но усложняет разработку.
И если в Python для вывода на экран сообщения «Привет, мир!» мы можем обойтись командой print("Привет, мир!"), то на ассемблере такая же программа
Введение в Python 8
будет выглядеть сложнее: section .data
hello:	db 'Привет, мир!',10
helloLen: equ J-hello
section .text
global _start
start:
mov eax,4
mov ebx,l mov ecx,hello mov edx,helloLen
int 80h
mov eax,l
mov ebx,0 int 80h,
Высокоуровневые языки, такие как Python, JavaScript, PHP и многие другие, делают процесс программирования значительно проще и быстрее. Они скрывают множество низкоуровневых деталей, позволяя программистам сосредоточиться на логике решения задачи, а не на особенностях работы процессора. Функции print() в Python или console. log() в JavaScript - яркие примеры удобства высокоуровневого языка по сравнению с отдельными командами на ассемблере.
Компилируемые и интерпретируемые языки программирования
Для того, чтобы компьютер понимал, что ему нужно сделать, исходный код любой программы должен быть переведён в машинный код. Для этого предназначен процесс трансляции. Трансляция программ, написанных на высокоуровневых языках, осуществляется двумя способами: с помощью компилятора или интерпретатора.
Представьте, что вы должны приготовить торт, рецепт которого написан на незнакомом вам языке. В таком случае вы выступаете в роли компьютера, а рецепт - это исходный код программы.
Компиляция похожа на заказ полного ггеревода рецепта в бюро переводов. Вы отдаете весь текст рецепта, ждете, пока его полностью переведут гга понятный вам язык, и только после этого начинаете готовить.
Компилируемые языки, такие как С, C++, Java или С#, сначала
Введение в Python 9
полностью переводятся в машинный код с помощью специальной программы -комп иля юра Результатом работы компилятора является исполняемый файл (бинарный код), который можно запустить на компьютере. Компиляция обычно обеспечивает более высокую скорость работы программы и больший контроль над ресурсами компьютера, но требует времени на предварительную сборку программы и часто привязывает программу к определенной операционной системе.
Интерпретация похожа на то. как если бы вы попросили друга, владеющего нужным вам языком, переводить вам рецепт в процессе готовки. Он переводит строку, вы выполняете действие, затем переходите к следующей строке и так далее.
Интерпретируемые языки, такие как Python, JavaScript или PHP. выполняются построчно с помощью специальной программы - интерпретатора. Интерпретатор читает, анализирует и тут же выполняет каждую строку кода. Такой подход упрощает процесс разработки и отладки, так как вы можете сразу видеть результат выполнения каждой команды, но может немного замедлить работу программы по сравнению с скомпилированным кодом.
Python является интерпретируемым языком, что делает его отличным выбором для начинающих, так как вы можете быстро писать и запускать код, сразу видя результат своих действий.
Итоги
S Программирование - это процесс создания программ.
J Программа - это последовательность команд, которые должен выполнить компьютер или другое устройство.
J Язык проз раммирования - это язык, предназначенный для записи программ.
J Синтаксис языка программирования - это правила записи команд в этом языке.
J По степени близости к машинному коду языки делятся на низкоуровневые (ассемблер), дающие прямой контроль над оборудованием, и высокоуровневые (Python), позволяющие абстрагироваться от деталей работы процессора.
S Трансляция кода в машинный код происходит путём компиляции (C++), при которой весь код заранее переводится в исполняемый файл, или интерпретации (Python, JavaScript), обеспечивающей построчное Введение в Python 10
выполнение кода.
Задания для самопроверки
1.	Что такое язык программирования?
2.	Что делает команда print ("Привет, мир! ") в Python?
3.	В виде последовательности каких чисел компьютер представляет данные?
4.	Но какому принципу языки программирования разделяются на высокоуровневые и низкоуровневые?
5.	Чем отличаются компилируемые языки программирования от интерпретируемых?
1.2 О ЯЗЫКЕ PYTHON
Давайте поговорим о Python - том самом языке программирования, который уже несколько лет удерживает звание самого популярного в мире. Его секрет кроется не только в понятном синтаксисе, ио и в универсальности. Python успешно используется в самых разных областях - от создания веб-сайтов до разработки искусственного интеллекта.
История Python
История Python началась в 1989 году с работы голландского программиста Гвидо ван Россума.
Рисунок 1. Гвидо ван Россум. Великодушный пожизненный диктатор, однако в 2018 году оз казался от должности
Введение в Python 11
Во время работы в Центре математики и информатики в Амстердаме он работал над созданием языка, который был бы простым в использовании, но при этом достаточно мощным. Его вдохновил язык АВС, разработанный для обучения программированию. От него Python унаследовал такую важную особенность. как обязательное использование отступов для определения блокон кода.
Название Python не связано со змеями, хотя такая ассоциация часто возникает из-за логотипа языка. На самом деле Гвидо ван Россум был большим поклонником британского комедийного шоу «Летающий цирк Монти Пайтона», и именно в честь него был назван язык.
Первая публичная версия Python 0.9.0 вышла в 1991 году, но полноценный релиз версии Python 1.0 состоялся только в 1994 году. Позже Гвидо переехал в США и продолжил работу над Python в Корпорации национальных исследовательских инициатив.
Вы наверняка замечали, что у программ и языков программирования есть номера версий, например. 3.9.12 или 3.11.5 Эти номера не случайны и несут определенную информацию о состоянии и изменениях в программе. Обычно номер версии имеет формат А.В.С. где:
о А - основной номер версии, увеличивается при значительных изменениях, которые могут нарушить обратную совместимость (с предыдущими версиями).
о В номер функциональных изменений, растет при добавлении новых возможностей и функций, которые обычно не ломают обратную совмести-
мость.
о С - номер исправления ошибок, указывает на небольшие обновления, такие как исправление найденных ошибок и улучшение стабильности.
Введение в Python 12
Сообщество и развитие
Важным моментом в истории Python стало основание в 2001 году некоммерческой организации Python Software Foundation (PSF). Эта организация взяла на себя ответственность за дальнейшее развитие и поддержку языка Python. PSF придерживается принципов открытого исходного кода, то есть Python является бесплатным и его код может свободно использоваться и модифицироваться любым желающим. Это способствует развитию языка благодаря усилиям большого и активного сообщества программистов по всему миру.
PSF также занимается созданием и распространением обучающих материалов. организует международные конференции и поддерживает переводы документации на разные языки. В 2023 году PSF была удостоена премии GitHub Awards как самое дружелюбное сообщество, что еще раз подчеркивает важность и силу Python-сообщества
Эпоха Python 3
Еще одной ключевой вехой в истории Python стал выход версии 3.0 в 2008 году. Эта версия внесла значительные изменения в язык, устранив некоторые фундаментальные недостатки и добавив множество новых возможностей. Однако одним из последствий этих изменений стала несовместимость с предыдущими версиями языка (в частности. Python 2).
Например, функция для вывода сообщения на экран, которая в Python 2 выглядела так:
print "Привет, мир!"
В Python 3 стала выглядеть следующим образом:
print( Пригет, мир! )
Планировалось, что Python 3 полностью заменит Python 2 к 2015 году, и обе версии будут поддерживаться параллельно до этой даты. Но из-за большого количества существующих на Python 2 проектов, которые было сложно быстро перевести на новую версию, поддержка старой версии была продлена до 1 января 2020 года. После этой даты обновления и исправления безопасности для Python 2 больше не выпускаются, что означает окончательный переход в эру Python 3.
Несмотря на это. Python 2 до сих пор можно встретить в некоторых устаревших системах и проектах, где переход на Python 3 оказался слишком сложным или затратным. Это касается, например, старых серверных приложений и
Введение в Python 13
специализированного программного обеспечения.
Тем не менее сейчас абсолютное предпочтение отдается Python 3 благодаря его стабильности, безопасности и постоянному развитию.
Области применения Python
Сегодня Python можно встретить практически в любой сфере разработки программного обеспечения. Многие известные компании, такие как Google, Яндекс, ВКонтакте и Netflix, активно используют Python в своих проектах. Сам по себе Python - это очень удобный и понятный язык, но невероятную силу ему придают сотни готовых инструментов - библиотек и фреймворков, которые уже написали другие программисты, и которые позволяют Python решать самые разные задачи, даже те, которые изначально не были встроены в сам язык.
Библиотеки - это наборы готовых функций для решения конкретных задач. Например, есть библиотеки для машинного обучения (Scikit-learn, SciPy) для сбора данных в интернете (BeautifulSoup, Selenium) или для работы с изображениями (Pillow). Вы можете использовать их функциональность в своих программах вместо того, чтобы писать всё с нуля.
Фреймворки - это более крупные и сложные инструменты, которые задают структуру программе и определяют, как будет происходить взаимодействие между ее частями. Они как готовый каркас для здания, который вы можете заполнить своими собственными кирпичиками. Фреймворки часто используются в веб-pa гработке (Django, Flask) или разработке мобильных и десктопных приложений (Kivy).
Однако каждый программист может создавать собственные решения, в том числе новые библиотеки и фреймворки, дополняя экосистему Python
Преимущества и недостатки Python
Как и любой инструмент, Python имеет свои сильные и слабые стороны.
Python является отличным выбором для широкою круга задач благодаря своей простоте, богатой экосистеме инструментов и мощной поддержке сообщества.
Однако важно учитывать его ограничения в производительности, которые могут быть критичны для некоторых высоконагруженных приложений (требующих максимальной скорости выполнения). Python является интерпретируемым
Введение в Python 14
языком, что означает, что он обычно медленнее компилируемых языков, таких как C++. Кроме этого, программы, написанные на Python, могут требовать больше оперативной памяти по сравнению с программами, написанными на других языках.
Таблица 1 - Преимущества и недостатки Python Преимущества Python	Недостатки Python
С	Простой и понятный синтаксис	* Производительность
£ Широкая область применения	J Потребление памяти
Независимость от операционной системы
< Множество готовых библиотек и
фреймворков
Большое и активное сообщество разработчиков
В целом. Python - прекрасный выбор для большинства задач, где скорость разработки и гибкость важнее абсолютной производительности. Для критически важных по скорости участков кода часто используются комбинации с другими языками, такими как C/C++.
Итоги
J Python создан голландским программистом Гвидо ван Россумом Первая публичная версия вышла в 1991 году.
Python назван в честь шоу «Летающий цирк Мон ги Пай гона».
J Python Software Foundation - это некоммерческая организация, которая развивает и поддерживает Python как проект с открытым исходным кодом.
J В 200К году вышла Python 3.0, которая принесла значительные изменения и нарушила обратную совместимость с Python 2. Поддержка Python 2 прекратилась в 2020 году.
✓ Благодаря большому количеству библиотек и фреймворков Python широко применяется в веб-разработке, сборе и анализе данных, машинном обучении, разработке мобильных и десктопных приложений и других областях.
S Преимуществами Python являются простой синтаксис, широкая область применения, кроссплатформенность, множество библиотек и активное сообщество.
Введение в Python 15
J Недостатками Python являются более низкая производительность и высокое потребление оперативной памяти.
Задания для самопроверки
1.	Кто является создателем языка программирования Python?
2.	Какая основная ветка Python (2 или 3) является актуальной для использования в настоящее время?
3.	В чём заключается разница между библиотекой и фреймворком?
4.	15 каких областях применяется Python?
5.	Перечислите основные преимущества и недостатки Python.
1.3 УС ТАНОВКА PYTHON И ПЕРВАЯ ПРОГРАММА
Итак, вы готовы писать свой первый код на Python. Но прежде чем мы окунемся в мир программирования, нам нужно подготовить компьютер. Для запуска программ, написанных на Python, необходима специальная программа - интерпретатор Python, который будет построчно читать и выполнять ваш код.
Поскольку поддержка Python 2 официально завершена, рекомендуется использовать актуальную версию Python 3 (3.12 и выше).
У становка интерпретатора Python
Процесс установки интерпретатора немного отличается в зависимости от вашей операционной системы. Давайте рассмотрим основные шаги для Windows, macOS и Linux.
Если вы используете Windows или macOS
Интерпретатор Python доступен для скачивания на официальном сайте -https://www.python.org/.
На главной странице (рисунок 2) найдите вкладку Downloads и нажмите на кнопку с названием последней версии Python 3 (например, Python 3.12.1), Начнется загрузка установочного файла. Если вам нужна другая версия Python, рядом с кнопкой за!рузки вы найдете ссылки для перехода на страницы с версиями для разных операционных систем.
После того как файл будет загружен, найдите скачанный файл и запустите
Введение в Python 16
его двойным щелчком мыши. Следуйте инструкциям (рисунок 3), которые будут появляться на экране.
AU releases
Download for Windows
About
Documentation
Community
Success Stories
News
Python 3.12 1
Source code
Windows
macOS
Othei Piatfoims
License
Alternative Implementations
Note that Pyth earlier.
cannot b« used on Windows 7 or
Not the OS you arelooking for? Python can oe used on many operating systems and environments.
View the full list of downloads.
Python is a programming language that lets you work quickly and integrate systems more effectively. >>> Learn More
Рисунок 2. Официальный сайт языка про1раммирования Python
йё Python 3.12.1 (64-bitj Setup
X
Install Python 3.12.1 (64-bit)
Select Install Now tc install Python with default settings, or choose Customize tc enable c r disable features
Includes IDLE, pip and documentation Cieates shr rtcuts and file associations
Install Now
К C:\Users\lrina\AppData\Local\Programs\Python\Python312
—> Customize installation
Chocse location and features
pljthon	use admin privileges when installing py.exe
windows*^ ®Aad py,n°"“e “₽ATH
Cancel
Рисунок 3. Установка Python на Windows
Введение в Python 17
Важно для пользователей Windows: на начальном этапе установки обязательно поставьте галочку напротив пункта «Add python.exe to PATH». Это позволит вам запускать Python из любого места в командной строке, а не только из папки, куда он установлен
Если вы используете Linux
Многие дистрибутивы Linux уже поставляются с предустановленным Python 3. Чтобы узнать, какая версия установлена, откройте терминал и введите команду:
python3 --version
Если вы увидите помер версии Python 3. который вас устраивает, установка не требуется.
Для установки другой версии Python 3 (например, 3.12) введите в терминале следующую команду (для систем на базе Debian, таких как Ubuntu):
sudo apt-get install python3.12
Может возникнуть ситуация, что на вашем компьютере будет установлено несколько разных версии Python 3 (например, 3.7 и 3.12), но по умолчанию будет использоваться не та, что вы хотите.
Для решения этой проблемы вы можете использовать утилиту update-alternatives. Например, вы можете присвоить версии 3.7 приоритет 1:
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 1
А версии 3.12 - больший приоритет, например, 2:
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/py-thon3.12 2
В утилите update-alternatives приоритет определяет порядок, в котором система выбирает, какую версию команды использовать по умолчанию. Чем выше приоритет, тем более вероятно, что данная альтернатива будет выбрана по умолчанию
Однако можно явно указать версию Python по умолчанию с помощью следующей команды:
sudo update-alternatives --config python3
Введение в Python 18
11осле этого на экране вы увидите примерно следующий список:
There аге 2 choices for the alternative python (providing /usr/bin/python).
Selection	Path	Priority		Status	
* 0	/usr/bin/python3.12	2	auto mode	
1	/usr/bin/python3.7	1	manual	mode
2	/usr/bin/python3.12	2	manual	mode
Press <enter> to keep the current choice[*], or type selection number:
Введите нужный номер (Selection) и нажмите Enter
Интерактивный режим Python
После успешной установки интерпретатора пришло время запустить наш первый код. Python - это интерпретируемый язык, а значит, мы можем писать и выполнять код прямо в терминале в интерактивном режиме.
Такой режим также называют REPL (от англ read-eval-prim-loop - прочи-тать-выполнить-напечатать-повторить), что отражает принцип его работы:
о Read - прочитать введенный код.
о Eval - выполнить этот код.
о Print - вывести результат выполнения.
о Loop - повторить этот цикл.
Для написания кода в интерактивном режиме нам понадобится открыть командную строку (в W indows) или терминал (в macOS и Linux). Эти приложения позволяют взаимодействовать с операционной системой с помощью текстовых команд.
Способ открытия отличается в зависимости от операционной системы: о в Windows используйте сочетание клавиш Win + R. введите emd и нажмите
Enter
о в macOS откройте Launchpad (Fn + F4), введите Терминал в поиске и щелкните по значку.
о в Linux используйте сочетание клавиш Ctrl + Alt + Т.
Чтобы войти в интерактивный режим, введите в терминале команду python (на Windows) или python3 (на macOS и Linux) и нажмите Enter.
Введение в Python 19
Важно для пользователей Linux и macOS: по историческим причинам обычно необходимо использовать команду python3 вместо python Если у вас установлено несколько версий Python, вы можете указывать конкретный номер, например, python3.12.
Если вы правильно ввели команду, то увидите приглашение в виде трех угловых скобок >>>. Это означает, что Python готов к работе:
PS С:\Users\Irina> python
Python 3.12.1 (tags/v3.12.1:2305ca5, Dec 7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on Win32
Гуре "help", "copyright", "credits" or "license" for more information.
>>>
Теперь давайте напишем нашу первую программу - выведем на экран уже знакомое сообщение «Привет, мир!» с помощью функции print():
>>> рг1пГ("Привет, мир!")
Но не вводите символы >>> - они уже отображаются в терминале в интерактивном режиме.
Нажмите Enter, и вы увидите результат;
Привет, мир!
Поздравляю! Вы успешно написали и запустили свою первую программу на Python!
Чтобы выйти из интерактивного режима, введите команду exit() и нажмите Enter, или просто закройте окно терминала.
Пакетный режим Python
Интерактивный режим отлично подходит для быстрых экспериментов и изучения отдельных команд. Однако его главный недостаток в том, что введенный код не сохраняется. Для создания полноценных программ, которые можно запускать многократно, используется пакетный режим. В этом режиме код пишется в файлах, которые затем выполняются интерпретатором.
Допустим, мы хотим выполнить несколько математических операций:
>» з + 2
5
Введение в Python 20
Но если позже нам понадобится снова выполнить эти же операции, то их придется вводить заново. Пакетный режим решает эту проблему, позволяя сохранять код.
В папке для ваших проектов на Python папке создайте новый файл и сохраните его иод именем hello.ру. Расширение .ру говорит о том, что это файл с кодом на языке Python, при этом имя такого файла должно быть написано латинскими буквами, без пробелов и специальных символов.
Откройте файл hello, ру в любом текстовом редакторе (например. Блокнот в Windows или TextEdit на macOS) и напишите в нем нашу знакомую команду:
print( Привет, мир1 )
Сохраните файл.
Теперь откройте терминал и перейдите в папку с файлом hello.ру Для этого используйте в команду cd Путь_к_папке (от англ, change directory - изменить папку). Например, если файл находится в C:\my_projects, введите:
cd C:\my_projects
Для запуска программы на Windows введите в терминале команду python, а на Linux и macOS - python3, а затем имя вашего файла:
python hello.ру
Нажмите Enter Интерпретатор прочитает и выполнит код из файла he! 1 о. ру, и вы снова увидите на экране’
Привет, мир!
Перенаправление вывода
Иногда бывает полезно не просто вывести на экране результат работы программы, а сохранить его в отдельном файле. Для этого используется перенаправление вывода с помощью символа >. Это не команда Python, а стандартная функция командной строки (терминала), которая работает со многими программами.
Давайте сохраним вывод программы hello.ру в файл output.txt, выполнив следующую команду (если вы используете Linux или macOS, то используйте python3 вместо python):
python hello.ру > output.txt
Эта команда создаст (или перезапишет, если он уже существует) файл output.txt в той же папке, где находится hello.ру, и запишет в нею результат Введение в Python 21
работы программы. На экране терминала при этом ничего не отобразится.
Если вы хотите добавить вывод программы в конец существующего файла, используйте двойной символ >>:
python hello.ру >> hello.txt
Теперь данные будут добавлены в конец файла и не перезапишут предыдущие записи.
1110111
Для написания и запуска кода на Python требуется установка интерпретатора Python (рекомендуется актуальная версия Python 3).
J Интерактивный режим позволяет построчно вводить, выполнять и сразу же видеть результат кода в терминале.
Пакетный режим позволяет сохранить код в файл с расширением .ру. и выполнить его целиком как полноценную программу.
J Вывод программы можно перенаправить в файл с помощью символов > или >>.
Задания для самопроверки
1.	Как называется инструмент, необходимый для запуска программ, написанных на Python?
2.	Как называется режим работы интерпретатора, в котором код можно писать в терминале и сразу же его выполнять?
3.	Какое расширение у файлов с кодом, написанным на Python?
4.	Что является основным назначением пакетного режима работы?
1.	4 РЕДАКТОР КОДЛ VISUAL STUDIO CODE
Ранее мы уже научились запускать через терминал программы на Python, написанные в Блокноте. Это полезно для простых задач и экспериментов, но для более серьезной разработки он неэффективен. Постоянное переключение между текстовым редактором и терминалом существенно замедляет работу.
Чтобы сделать процесс написания кода более эффективным и приятным, программисты используют специальные программы - редакторы кода, которые обладают множеством полезных функций, таких как.
Введение в Python 22
о Подсветка синтаксиса, которая выделяет разные элементы кода разными цветами, что облегчает чтение и написание программы
о Автодополнение кода, когда редактор предлагает варианты завершения набираемых слов и конструкций, что ускоряет написание кода и помогает изб ежа! ь опечаток.
о Отладка кода с помощью встроенных инструментов, которые позволяют пошагово выполнять программу и находить ошибки.
о Интеграция с системами контроля версий (например. Git) для удобного управления изменениями н коде и совместной работы над проектами.
о Поддержка множест ва языков программирования
о Расширяемое! ь путём добавление новых функций с помощью плагинов (расширений).
Одним из самых популярных редакторов кода на сегодняшний день является Visual Studio Code (VS Code) от Microsoft. Он полностью бесплатный, работает на всех основных операционных системах, поддерживает огромное количество языков программирования и обладает мощными встроенными инструментами. Кроме того, его возможности можно расширить практически до бесконечности с помощью большого каталога бесплатных расширений.
Установка VS Code
VS Code доступен для скачивания на официальном сайте -https://code.visualstudio.com/. На главной странице нажмите кнопку Download for Ваша_Операционная_ Система и скачайте установочный файл.
The open source Al code editor
Download for Windows
Web Insiders edition or other platforms
By using VS Code, you agree to ftshcense and onvaev statement-
Рисунок 4. Официальный сайт VS Code
Введение в Python 23
Установка VS Code на Windows и Linux:
1.	Откройте загруженный установочный файл.
2.	Следуйте инструкциям на экране для завершения установки.
Установка VS Code на macOS:
I Разархивируйте файл VSCode-osx.zip
2.	Перетащите Visual Studio Code.app в пайку Applications.
3.	Добавьте VS Code в Dock, выбрав в Options пункт Keep in Dock.
При первом запуске VS Code вы увидите приветственный экран. Можете ознакомиться с ним и закрыть его - сейчас он вам не понадобится.
Подготовка VS Code к работе
Настройка языка интерфейса VS Code
Для комфортной работы и поиска в интернете ответов на возникающие вопросы рекомендуется использовать английский язык интерфейса VS Code. Если у вас установлен другой язык по умолчанию, то изменить его можно следующим образом:
1.	Нажмите сочетание клавиш Ctrl + Shift + Р, чтобы открыть командную палитру.
2.	В появившемся поле введите команду Configure Display Language и нажмите Enter
3.	Выберите English из списка предложенных языков. VS Code предложит перезапуститься для применения изменений. Нажмите Restart.
Установка расширения Python
Для программирования на Python в VS Code необходимо установить специальное расширение (рисунок 5):
1.	На левой боковой панели найдите значок Extensions (выглядит как четыре квадратика) и щелкните по нему.
2.	В строке поиска введите python.
3.	В списке результатов найдите расширение Python, разработанное компанией Microsoft Обратите внимание на автора - это важно для установки официального и проверенного расширения.
4.	Нажмите на кнопку Install рядом с этим расширением.
Введение в Python 24
python
Python Indent Ф 74
Correct Python indent Kevin R&ic
Edit Selection
Python Ext» O7JM *<s
Popular Visual Studro Code ...
Don J»y*rr.*nne Mfl
Python Envir Ф йзм ★ 3.S
View and manage Python e...
Don Jbyamanne	Mee
Python Ф1094М *4
InteflrSense (Pylance), ф Microsoft 1пЖд^.
» Extension Python X
.M Search [Administrator]
PythOn "023.22.1
Microsoft Ф microsoft.com
IntefliSense (Pylance), bnling. Debugging
DETARS FEATURE CON
I
Ф 109.4
changelog extension pack
Python extension for Visual Studio Code
------r <11 A ryUKnl TOT V3... *V ЭАМ W i.
Python language extension -Th- ws rUckm. ***• Д
»>ii> autoDocstring Ф t<m vs doc Generates python docstrmg.
""" NHs Werner	
J*. Python	<s> 05s • s
Extensions for Python 3^^ shi*-	rnsui
A s isual Studio t. oae extension wrtn rich supprvt for me Python language (for al actively supported versions of the language >=37), including features such as inteiliSense (Pylance; bnting, detwggng code navigation code fcirnatbng retactorng vanahle explcvei test exptoin Mid more
Support for vscode.dev
The Python extension does offer some support when running
Рисунок 5. Установка расширения Python
Открытие папки проекта
Теперь нам нужно указать VS Code, где будут храниться наши файлы с кодом (рисунок 6):
1. В верхнем меню выберите File —> Open Folder...,
2. В появившемся окне создайте новую папку для ваших Python-проектов (например, my_python_projects) или выберите уже существующую. Нажмите кнопку Select folder.
View Go Run Terminal Hei Welcome - V<
Ctri+N
Ctri+Aft+Wmdows+N
Ctrl+Shift+N
Ctri+O
Ctrif-KCtrl+O I Code
Open workspace from Hie.. Open Recent
Arid folder to Workspace Save Workspace As Duplicate Workspace
Save
Ctri+S
Рисунок 6. Открытие папки проекта
Введение в Python 25
VS Code может спросить вас о доверии к авторам файлов в этой папке (рисунок 7). Поставьте галочку напротив Trust the authors of all files in the parent folder (с англ. - Доверять авторам всех файлов в родительской папке) и нажмите Yes, 1 trust the authors (с англ. - Да. я доверяю авторам).
Это сделано для того, чтобы защитить вас от потенциально небезопасною кода, который вы можете скачать в интернете и открыть в VS Code.
Do you trust the authors of the files in this folder?
Code provides features that may automatically execute files in this folder.
If you don't trust the authors of these files, we recommend to continue in restricted mode as the files may be malicious. See our docs to learn more
С:\иу^projects
trust fotoei ana enable oU featurn
Bmtf folder <n restricted mode
Рисунок 7. Вы же доверяете собст венной папке?
Наст ройка автосохранения
Чтобы избежать потери написанного кода в случае неожиданного сбоя программы или отключения электричества, важно настроить автоматическое сохранение изменений в VS Code (рисунок 8):
I В верхнем меню выберите File —> Preferences —* Settings или используйте сочетание клавиш Ctrl + , (запятая).
2. В открывшемся окне настроек введите в поисковой строке auto save.
3. В разделе Files: Auto Save выберите параметр afterDelay, который отвечает за автосохранение файла после небольшой задержки. Задержка по умолчанию составляет 1000 мс, но вы можете изменить её но своему усмотрению.
Введение в Python 26
♦ hello.py
f settings X
commonly used (1)
v Text Editor (3J
Files (3)
Wortbench (I)
«о и -
СЙ	Files Auto Save
Controls rule save of editors that have unsaved changes
afterUelay	v
v Extensions 11)
Python (1)
oft afterDelay onFocusChange
onWindo«Change
An edrtca лип changes is automatically ‘aven alter the configured "Fries Auto Save Delay" controls uie treray rrnnmisecunas апвг wreui an «morwith unsaved changes is saved automatically Only applies when Hies Auto Save is set to aFterDelay.
1000
Рисунок 8. Настройка автосохранения
Первая программа в VS Code
Давайте создадим первый файл с кодом в VS Code (рисунок 9):
1.	Откройте вкладку Explorer и наведите на неё курсор.
2.	В появившейся верней панели нажмите на значок New File... (выглядит как лист бумаги с плюсом).
3.	Введите имя файла hello.py и нажмите Enter
Рисунок 9. Файл с кодом на Python имеет расширение .ру
В открывшемся файле hello.py напишите уже знакомую команду для вывода сообщения на экран:
print("Привет, мир! )
Введение в Python 27
Теперь запустите программу. Есть несколько способов это сделать:
о Используйте сочетание клавиш Ctrl + F5.
о Нажмите на кнопку Run Python File в виде треугольника О (рисунок 10).
hello ру my_projects - Visual Studio Code [Administrator] П Q ГП OS — О X
EXPLORER
v MV PROJECTS
♦ hello.py
♦ hello.py X
1 print(”npwBer, мир!")
2 I
PROBLEMS TERMINAL •••
□ Pytnoc J- ~ g ... л x
PS C:\Users\my_projects> & C:/users/Irina/AppData/Local/Progr ams/Python/Python312/python. exe c: /(Jsers/my projects/hello. py
Привет, мир!
Рисунок 10. Запуск первой программы в VS Code
Результат выполнения программы отобразится во встроенном терминале в нижней части окна VS Code. Если терминал случайно закрылся, его можно открыть с помощью сочетания клавиш Ctrl + Shift + ' (обратный апостроф, совмещён с буквой «ё» на клавиатуре) или через меню Terminal —► New Terminal
В той же нижней области вы найдете вкладку PROBLEMS с сообщениями об ошибках и предупреждениями в коде. Например, если вы удалите закрывающую кавычку в нашей программе, то увидите сообщение, указывающие на незакрытую строку (рисунок 11).
’• hello.py 2 X
1	print .	. мир ! )
2
PROBLEMS г OUTPUT 9EBUG CONSOLE TERMINAL
v ♦ hello.py 2
® '(“ was not closed Pylance [Ln 1 Col 6]
® Stnng literal is unterminated Pylance [Ln 1 Col 7]
Рисунок 11. Одна забытая кавычка приводит к двум ошибкам
Итоги
J Для комфортной разработки программисты обычно используют специаль-
ные программы - редакторы кода.
Введение в Python 28
J Visual Studio Code (VS Code) oi Microsoft - один из самых популярных и функциональных редакторов кода.
Для полноценной работы с Python в VS Code требуется установить официальное расширение Python (разработано Microsoft).
S В VS Code рекомендуется настроить автосохранение для предотвращения потери написанного кода.
J Результат выполнения кода и сообщения об ошибках отображаются во встроенном терминале и вкладке PROBLEMS.
Задания для самопроверки
1	Что такое VS Code?
2.	Как запускается код в VS Code?
3.	Для чего используется вкладка PROBLEMS в VS Code?
1.5 РЕКОМЕНДАЦИИ ПО ОФОРМЛЕНИЮ КОДА
Когда мы пишем программы, важно не только то. что делает код. ио и то. кик он выглядит. Хорошо оформленный код легче читать, понимать, отлаживать и поддерживать. Для языка Python существует официальное руководство по стилю кода под названием РЕР 8 (от англ. Python Enhancement Proposal - Предложение по улучшению Python).
PEP 8 представляет собой обширный документ, включающий множество рекомендаций по стилю и оформлению кода на Python. Однако, поскольку мы только начинаем изучение Python, мы рассмотрим только самые основы, такие как:
о Базовые правила отступов и форматирования.
о Советы по использованию пробелов и длин строк.
о Важность аккуратного комментирования и соблюдения регистра.
Отступы
В Python отступы играют ключевую роль в определении блоков кода, которые представляют собой группы логически связанных выполняемых как единое целое. Начало блока кода обозначается увеличением отступа, а конец - возвратом к предыдущему уровню отступа. Согласно РЕР 8, для отступов следует
Введение в Python 29
использовать 4 пробела.
В примере ниже отступы определяют, какие действия выполняются, если условие истинно:
candy_count = 10
if candy_count > 0:
print( с вазе еще есть конфеты.")
print( Можете взять одну.”)
candy_count = candy_count - 1
Здесь инструкции с функциями print () и уменьшением значения переменной candy_count находятся внутри блока кода, который выполняется только в том случае, если значение переменной candy_count больше 0 Отступ в 4 пробела четко показывает, какие инструкции принадлежат этому блоку.
При определении отступов крайне не рекомендуется использование символа табуляции, а также запрещается смешивать пробелы и табуляцию. Даже малейшее нарушение правил отступов приведёт к ошибке IndentationFrror (с англ. - ошибка отступа), поскольку Python строго следит за структурой кода именно благодаря отступам.
В VS Code при нажатии на клавишу ГаЪ по умолчанию вставляется 4 пробела вместо символа табуляции, что избавляет от необходимости прописывать их вручную.
Длина строки
По возможности ограничивайте длину каждой строки до 79 символов. Длинные строки ухудшают восприятие и затрудняют чтение кода на небольших мониторах или устройствах с низким разрешением экрана.
Если выражение становится слишком длинными, Python предлагает несколько предпочтительных методов переноса.
Использование скобок
Если выражение заключено в круглые, квадратные, или фигурные скобки, его можно переносить на любое количество строк без использования специального символа переноса. Python автоматически понимает, что выражение не закончено.
Например, если вы работаете с длинной строкой, то просто разбейте её на части и заключите их в скобки. Python объединит их автоматически:
citate = ( Когда потеряна истинная добрсдетель, является добродушие; ”
Введение в Python 3(1
" когда же потеряна справедливость, является приличие. Правила приличия " эти только подобие правды и начало всякого беспорядка.")
Для визуального выделения продолжения выражения рекомендуется использовать отступ в 4 пробела (стандартный отступ Python).
Использование символа обратного слэша
Когда выражение нельзя заключить в скобки, используется символ обратного слэша (\), который указывает интерпретатору Python, что следующая строка является продолжением текущей.
Например, это позволит разбить выражение result = 136 + 839 - 892 * 2-1 следующим образом: result = 136 + 839 \ - 892 * 2 \ - 1
Если вы случайно добавите пробел после символа \, это вызовет ошибку SyntaxError (с англ. - синтаксическая ошибка). Поэтому РЕР 8 настоятельно рекомендует использовать скобки везде, где это возможно, и оставлять символ \ только для ситуаций, где скобки неприменимы.
Порядок мнет рукций
В Python каждая отдельная команда (инструкция) обычно располагается на новой строке:
print( Привет, мир! )
print( Как дела?")
При этом существует способ размещения нескольких инструкций на одной строке через точку с запятой:
print( Привет, мир! ); print('Как дела?")
Но такой стиль крайне не рекомендуется для разработки программ, так как ухудшает читаемость. Это может быть допустимо лишь в редких случаях, например, при работе в интерактивном режиме.
Кавычки
В Python для обозначения строк можно использовать как одинарные ('), так и двойные (") кавычки. С точки зрения интерпретатора, разницы между ними
Введение в Python 31
нет:
print( Привет, мир! ) print(Привет, мир!')
Оба варианта выведут на жран одно и то же сообщение:
Привет, мир!
Удобство использования разных видов кавычек проявляется, когда внутри строки необходимо использовать сами кавычки:
РЕР 8 не устанавливает строгих правил относительно выбора между одинарными и двойными кавычками. Главное - придерживаться одного стиля в рамках всего вашего проекта. Если вы начали использовать для строк двойные кавычки, то старайтесь использовать их и дальше.
Регистрозависимость
Python является pei истрозависииыч языком. Это означает, что строчные и прописные буквы в именах переменных, функций и других ключевых словах воспринимаются как разные символы.
Например, функция для вывода текста на экран называется print(). Если вы напишете Print () или PRINT(), Python не сможет распознать эту команду, что приведёт к ошибке NameError (с англ. - ошибка имени).
Итоги
•J РЕР 8 - официальное руководство по стилю кода на Python.
J Отступы в Python (рекомендовано 4 пробела) определяют блоки кода.
J Длина строки по возможности не должна превышать 79 символов
J Каждая инструкция должна располагаться на новой строке
J Для строк можно использовать как одинарные ', так и двойные " кавычки.
J Python - регистрозависимый язык, то есть различает строчные и заглавные буквы.
Задания для самопроверки
1.	Что такое блок кода?
2.	Сколько пробелов рекомендуется использовать для отступа?
3.	Как следует размещать инструкции в коде?
Введение в Python 32
4.	Какие виды кавычек можно использовать для строк в Python?
5.	Что произойдёт, если написать PRINT () вместо print() для вывода сообщения на экран?
1.6 КОММЕНТАРИИ
Не весь код, который мы пишем, предназначен для непосредственного выполнения компьютером. Иногда нам нужно оставлять пояснения, заметки или временно отключать фрагменты кода. Для этого в npot раммировании используются комментарии
В Python однострочные комментарии начинаются с символа решетки (#). Все, что следует за ним до конца строки, игнорируется интерпретатором. Это позволяет добавлять в код объяснения или напоминания:
print( Привет, мир! ) # Это поясняющий комментарии
Также в комментариях можно указать ожидаемый результат выполнения кода:
print( Привет, мир1')
#	Вывод: Привет, мир!
Кроме пояснений комментарии позволяют быстро отключить отдельные участки кода без их удаления:
#	Раскомментируйте эту команду, чтобы вывести сообщение
#	print("Заблокированное сообщение")
Но если символ # встречается внутри строки, то он рассматривается как её обычный символ и не интерпретируются как начало комментария
print( Однострочный комментарий начинается с символа #")
#	Вывод: Однострочный комментарий начинается с символа /г
Рекомендации РЕР 8 по оформлению комментариев
Следование рекомендациям РЕР 8 при написании комментариев делает код более читаемым и профессиональным:
1	Ставьте пробел между символом # и текстом комментария:
# Правильное пространство перед комментарием
2.	Если комментарий размещён рядом с командой на одной строке, оставляйте 2 пробела между самой инструкцией и началом комментария:
х = 1 + 2 # Результат вычисления сохраняем в переменную х
Введение в Python 33
3.	Комментарии желательно располагать на уровне отступа блока кода, которому они принадлежат:
def my_function(x):
# Функция умножения на 2 положительных чисел if х >0:
# Блок обработки положительных значений result = х * 2
Многострочные коммент арии
Иногда возникает необходимость записать более развернутое пояснение, которое не умещается в одну строку. Для этого можно разместить столько строк с символом #, сколько требуется:
#	Это пример многострочного комментария.
#	Он используется для предоставления более подробного
it объяснения определенного участка кода или его логики
print('Bce ещё привет, мир!")
#	Вывод: Всё ещё придет, мир!
Но вместо множества символов # для длинных комментариев часто используются многострочные строки, которые создаются с помощью тройных одинарных или двойных (""") кавычек:
"""Этот блок описывает работу модуля и служит примером стиля оформления документации для крупных блоков кода.
II II II
print( Сколько можно приветствовать мир? )
#	Вывод: Сколько можно приветствовать мир?
Технически это не комментарии, а строки, которые интерпретатор игнорирует, если они не присвоены переменной. Однако этот способ стал общепринятым для написания строк документации (англ, - docstrings). Они служат для описания назначения и функциональности объектов Python, таких как модули, функции и классы. Такие конструкции похожи на многострочные комментарии, но имеют ключевое различие: они хранятся в памяти программы и могут использоваться инструментами разработки для создания документации.
Когда и что комментировать?
Комментарии должны помогать понять логику и назначение кода, поэтому следует придерживаться определённых рекомендаций:
1.	Объясняйте причину выбора конкретного подхода или решения:
Введение в Python 34
# Используем кэширование результатов для ускорения (S)lru_cache(maxsize=128) def fibonacci(n):
2.	Добавляйте ясность там, где логика сложна или неочевидна:
# Алгоритм Евклида для нахождения НОД двух чисел while а != Ь:
3.	Документируйте функции, классы и модули:
def congratulate():
..Выводит на экран сообщение с поздравлением >1 п н
print("Пусть твой день будет полон счастья и радости!")
4.	Избегайте избыточного комментирования очевидных действий: print("Привет, мир! ) # Вывод на экран сообщения "Привет, мир!"
Хорошо написанные комментарии делают код более понятным и облегчают его поддержку в будущем.
Итоги
J Комментарии - это текст в коде, который игнорируется интерпретатором и не выполняется. Они используются для пояснений или временного отключения кода.
Однострочные комментарии начинаются с символа #.
J Если комментарий размещён на той же строке, что и код, следует оставлять 2 пробела между инструкцией и символом #.
J Между символом # и текстом комментария ставится 1 пробел. Комментарии рекомендуется выравнивать по отступу блока кода.
J Многострочные комментарии создаются с помощью тройных одинарных (' ") или двойных (""") кавычек
J Строки документации - это многострочные комментарии в тройных кавычках для описания модулей, функций и классов.
Задания для самопроверки
1.	С какого символа начинается однострочный комментарий?
2.	Для чего нужны комментарии?
Введение в Python 35
3.	Как можно написать коммензарий. занимающий несколько строк?
4.	Используйте символ # и создайте простой комментарий на тему вашего любимого хобби или блюда.
5.	Запишите свою любимую цитату или стихотворение в виде многострочного комментария.
Введение в Python 36
Глава 2
Основы Python
2.1 ПЕРЕМЕННЫЕ
Сложно представить написание программы без работы с данными, например. числами, текстом или целыми коллекциями. Для того чтобы временно хранить эти данные и иметь возможность многократно использовать их в программе, предназначены переменные.
Переменная представляет собой символическое имя (метку), которое указывает на определенное значение (объект), хранящееся в памяти компьютера. Значение переменной может изменяться в процессе выполнения программы.
Создание переменной и оператор присваивания
Для создания переменной в Python необходимо присвоить ей некоторое значение с помощью оператора присваивания =. Этот оператор работает следующим образом: значение, находящееся справа от символа =, записывается (присваивается) переменной, имя которой указано слева.
название_переменной = значение_переменнсй
То есть оператор присваивания связывает имя переменной с некоторым значением.
В программировании операторы выполняют определенные действия (операции) над данными, которые называются операндами. Рассмотрим пример:
message = "Привет, мир!"
Здесь оператор = связывает переменную message со строкой "Привет, мир!".
Способы присваивания значения переменным
Python поддерживает разные типы данных, например, строки и числа. При этом строки заключаются в кавычки:
first_name = "Иван"
Основы Python 37
last_name = "Рюрикович"
Целые и вещественные числа, наоборот, указываются без кавычек: year = 2024 weight = 60.5
Значением переменной может быть не только конкретное значение, но и результат вычисления какого-либо выражения: year = 2024 + 1
Python также поддерживает множественное присваивание, позволяя присвоить значения сразу нескольким переменным в одной строке, разделяя их запятыми:
month, day = 1, J8 print(month) # Вывод: 1 print(day) # Вывод: 18
Это часто используется для обмена значениями переменных: day, month = month, day
Теперь в переменной day хранится значение month и наоборот: print(month) # Вывод: 18 print(day) # Вывод: 1
Кроме того, можно присвоить одно и то же значение нескольким переменным одновременно, используя каскадное присваивание: hours = minutes = seconds = 10
Значения таких переменных совпадают: print(hours) # Вывод: 1в print(minutes) # Вывод: 1& print(seconds) # Вывод: 10
Как создаю гея переменные в Python?
Рассмотрим создание переменной с именем book и строковым значением "Приключения Шерлока Холмса": book = 'Приключения Шерлока Холмса"
Основы Python 38
Здесь мы как бы говорим Python «Создай в памяти место для хранения текста “Приключения Шерлока Холмса" и свяжи это место с именем book». Это похоже на то, что мы взяли коробку, написали на ней book и положили внутрь книгу "Приключения Шерлока Холмса". Позже мы можем присвоить этой же переменной другое значение:
book = "Винни-Пух"
Тогда «книга» в «ящике» book заменится на "Винни-Пух".
Однако на самом деле переменная в Python - это не сам «ящик» с данными, а ссылка на него. Когда мы присваиваем значение переменной. Python создает в памяти объект, представляющий это значение, а переменная просто указывает на этот объект.
В Python любые данные - будь то числа, строки, списки или функции - представляют собой объекты. Каждый объект характеризуется тремя основными параметрами: идентификатором (уникальным номером), типом (например, число, строка) и значением (содержанием).
У каждого объекта есть свой идентификаюр - уникальный номер, представляющим собой адрес этого объекта в памяти компьютера. Узнать уникальный идентификатор объекта в памяти компьютера позволяет функция id().
Функция
Описание
Параметры
Возвращаемое значение
id(object)
Возвращает уникальный идентификатор (адрес в памяти) объекта
object - объект, идентификатор которого требуется получить
Целое число, представляющее идентификатор объекта
Например, выведем на экран идентификатор переменной book'
Основы Python 39
book = "Винни-Пух" print(id(book)) # Вывод: некоторое целое число, например: 140717426031032
Этот идентификатор является уникальным для данного объекта в текущий момент времени. Но если вы запустите этот код ещё раз, то объект book будет создан заново и вы увидите другое число, например, 1435294094646 или 1792272815408.
У переменных, которые были созданы с помощью каскадного присваивания, будут одинаковые идентификаторы, так как они ссылаются на один и тот же объект в памяти:
bookl = book2 = ЬоокЗ = "Гамлет1
print(id(bookl))
print(id(book2))
print(id(ЬоокЗ))
# Вывод: одинаковое число во всех случаях, например: 140717537114584
Память компьютера
"Винни-Пух"
book
>140717426031032
"Гамлет"
bookl
Ьоок2
140717537114584ч
ЬоокЗ
Рисунок 12. На один объект может ссылаться несколько переменных
Здесь bookl, book2 и ЬоокЗ - это просто три разных имени, указывающих на одну и ту же область памяти, где хранится строка "Гамлет"
Правила именования переменных
Правильный выбор имен для переменных - важная часть написания читаемого и поддерживаемого кода. В Python существуют определенные правила, которым должны соответствовать имена переменных:
1.	Имя переменной может содержать только латинские буквы, цифры и
символ подчеркивании (_):
quizl = "Сколько звезд на небе?"
Основы Python 40
do_it_again = "Функция, которая делает что то важное"
2.	Имя переменной не может начинаться с цифры: # Так делать нельзя lbook = "Слово о полку Игореве" # Не может начинаться с цифры
3.	Пробелы, точки и другие специальные символы недопустимы # Так делать нелозя customer.age = 23 # Имя не может содержать точку user email = "romanov@mail.ru" # Имя не может содержать пробел
4.	Имя переменной не может совпадать с ключевыми словами Python - зарезервированными словами, которые имеют специальное значение в языке: False, None, True, and, as, assert, async, await, break, class, continue, def, del, elif, else, except, finally, for, from, global, if, import, in, is, lambda, nonlocal, not, or, pass, raise, return, try, while, with, yield
He стоит пугаться большого списка ключевых слов или заучивать его. так как позже вы подробно познакомитесь с большинством из них: # Так делать нельзя class = "БоЬевые" # Не может совпадать с ключевыми словами
5.	Переменные, состоящие из нескольких слов, принято именовать в змеином pei истре (англ. - snake case). Слова в таком стиле пишутся строчными буквами и разделяются символами подчеркивания: custom_age = 23 user_email = "romanuv@romanoff.ru" number_of_apples = 12
6.	Имя переменной должно быть осмысленным и отражать ее назначение. Хорошие имена делают код более понятным и уменьшают необходимость в комментариях. Вместо того чтобы использовать короткие и непонятные имена (например, х, n, val), старайтесь давать переменным описательные имена (например, customer_name, item_count, total_price): height = 180 # Лучше, чем h или my_big_tower number_of_stuaents = 25 # Лучше, чем п или ihwer8
Примеры
Пример I. Данные о пользователе на сайте
В профиле на сайте пользователь указывает имя. элсктройную почту и возраст. Переменные позволяют не только хранить эти данные, но и изменять их:
Основы Python 41
#	Создаем переменные для хранения данных пользователя user_name = ‘Екатерина" # Имя (строка)
user_email = "catherine@mail.ru" # Почта (строка) userage =28 # Возраст (целое число)
#	выводим данные о пользователе
print('Имя: , user_name) print('Почта: , user_emaii) print(Возраст:", user_age)
#	Допустим, пользователь решил обновить свою почту
user_email = "catherine_the_great@mail.ru" # Новая почта (строка)
#	Выводим информацию об изменении почты
print('Почта пользователя , user_name, "изменена на', user_email)
Вывод:
Имя: Екатерина
Почта: catherine@mail.ru
Возраст: 28
Почта пользователя Екатерина изменена на catherine_the_great@mail.ru
Пример 2. Расчет стоимости заката в интернет-ма> азине
В интернет-магазине общая стоимость заказа складывается из суммы стоимостей каждого товара с учётом его количества:
#	Цены и количество товаров в заказе
price_yogurt =56.5 # Цена йогурта за штуку (число с плавающей точкой)
price_chocolate = 98 # Цена шокопадки за штуку (целое число)
count_yogurt =5 Количество йогурта (целое число)
count_chocolate = 3 # Количество шоколадок (целое число)
#	Рассчитываем стоимость каждого типа товара +otal_yogurt_cost = price_yogurt * count_yogurt total_chocolate_cost = price_choccLate * count_chocolate
#	Рассчитываем общую стоимость заказа
total_order_cost = totai_yogurt_cost + total_chocolate_cost print(f"Общая стоимость заказа: , total_order_cost)
Здесь значение переменной total_order_cost является результатом арифметического выражения, включающего другие переменные.
Вывод:
Общая стоимость заказа: 576.5
Основы Python 42
Пример 3. Обмен значениями координат гонки
В двумерной координатной системе точка задана координатами х и у, которые необходимо поменять местами. Python позволяет это сделать в одно действие с помощью множественного присваивания
#	Заоаём координаты точки
X	= 10
у	= 20
#	Выводим на экран исходные координаты
print( Исходная координата х:"., х)
print( Исходная координата у. , у)
#	Используем множественное присваивание и меняем значения местами
#	Теперь в х будет значение у} а в у - х
X, у = у, X
#	Выводим на экран новые координаты print('Новая координата х л х) print('Новая координата у:", у)
Вывод:
Исходная координата х: 10
Исходная координата у: 20
Новая координата х: 20
Новая координата у: 10
При этом классически задачу обмена значений двух переменных решают через временную (промежуточную) переменную:
#	Задаем координаты точки
х = 10
у = 20
#	Выводим на экран исходные координаты
print( Исходная координата х: ", х)
print("Исходная координата у:", у)
#	Не используем множественное присваивание., а создаём новую переменную
#	temp хранит значение х, которое затем присваивается у
temp = х
х = у
у = temp
#	Выводим на экран новые координаты
print(Новая координата х:", х)
print( Новая координата у:", у)
Итоги
J Переменная - это символическое имя, которое ссылается на определенное значение, хранящееся в памяти компьютера.
Основы Python 43
S Создание переменной: происходит с помощью оператора присваивании =, который связывает имя переменной со значением.
В Python переменная - это ссылка на объект в памяти. При этом несколько переменных могут ссылаться на один и тот же объект.
J Функция id() позволяет узнать уникальный идентификатор объекта в памяти компьютера.
S Имена переменных должны быть осмысленными и могут содержать только латинские буквы, символ нижнего подчеркивания и цифры. При этом имя не может начинаться с цифры.
Задания для самопроверки
1.	Что такое переменная в Python?
2.	Как создать переменную password со значением "qwerty"?
3.	Как одновременно создать несколько переменных movie 1, movie2 и movie3 с одинаковым значением "300 спартанцев"?
4.	Что будет выведено на экран в результате выполнения данного кода?
like = "Яблоки"
dislike = 'Груши'
like, dislike = dislike, like
print(like)
5.	Совпадают ли идентификаторы следующих переменных?
game = book = movie = "Assassin's Creed"
6.	Исправьте ошибки в именах переменных:
1 group = "Рлмашки"
True = "Истина в вине" пользователь = "flower" custom settings = "Дополнительные настройки" school.database = 'База данных школы"
2.2 ТИПЫ ДАННЫХ
Тип данных переменной определяет, какие виды значений она может хранить и какие операции над этими значениями можно выполнять. Когда мы создаем переменную и присваиваем ей значение, интерпретатор Python автоматически определяет ее тип на основе этого значения. Например, для чисел доступны арифметические операции, а для строк - специальные строковые методы.
Основы Python 44
Правила, определяющие, как язык npoi раммирования работает с различными типами данных, называются типизацией
Типизация в языках программирования
В программировании существуют различные подходы к типизации, которые можно классифицировать по нескольким критериям.
Сильная и слабая типизация
Эта характеристика определяет, насколько строго язык контролирует смешение различных типов данных в одном выражении.
Языки с сильной типизацией (например. Python, Java, С#) требуют явное преобразование типов (ещё называемое их приведением) перед выполнением операций над несовместимыми типами данных. Поэтому попытка сложить число и строку в Python приведет к ошибке ТуреЕггог: 12 + "5" ff Ошибка: ТуреЕггог: unsupported operand type(s) for +: 'int' and 'str'
Даже если значение в кавычках содержит число, интерпретатор рассматривает его как строку и не выполняет автоматическое преобразование.
Языки со слабой типизацией (например, JavaScript. PHP) более лояльны к смешению типов и часто выполняют автоматическое (неявное) преобразование типов, поэтому, например, в JavaScript мы можем сложить число со строкой:
12 + "5"; //В результате получится "125"
Здесь число 12 автоматически преобразуется в строку "12". после чего будет выполнена конкатенация (сложение) строк.
Явная и неявная типизация
Эта характеристика определяет, нужно ли явно указывать тин переменной при ее создании.
Языки с явной типизацией (C++, С#, Java) требуют явное указание типа переменной при ее объявлении. Например, в C++ следует указывать тип int для целого числа и std:: string для строки:
int age = 26; // Явное указание типа (целое число) std::string name = "Михаил"; // Явное указание типа (строка)
Языки с неявной типизацией (Python, JaxaScript. PHP) позволяют создавать
Основы Python 45
переменные без явного указания их типа. Так интерпретатор Python еам определяет тип переменной на основе присваиваемого значения:
age = 26 # Автоматическое определение типа как целого числа name = "Михаил" # Автоматическое определение типа как строки
Неявную типизацию иногда называют «утиной типизацией» - если что-то выглядит как утка, плавает как утка и крякает как утка. то. вероятно, это и есть утка. То есть, тип объекта определяется не его явным объявлением, а его поведением (набором доступных методов и свойств).
Динамическая и статическая типизация
Эта характеристика определяет, когда происходит определение типа переменной.
В языках с динамической типизацией (Python, JavaScript, PHP) тип переменной определяется во время выполнения программы, когда ей присваивается значение. В Python одна и та же переменная может последовательно хранить значения разных типов:
length =25 # Тип Length - целое число
length = ’Бесконечная" # Теперь тип Length - строка
В языках со статической типизацией (C/C++, С#, Java) тип переменной должен быть указан при её объявлении и не может быть изменен после:
int length = 25; // Тип Length - целое число
length = "Бесконечная"; // Ошибка, так как Length может быть только числом
Здесь на языке C++ переменная length объявлена как целое число, поэтому присваивание ей позже строкового значения приведёт к ошибке при компиляции.
Основные встроенные тины данных
Встроенные типы данных в Python можно условно разделить на несколько категорий в зависимости от их основного назначения:
о Числа используются для арифметических операций.
о Строки служат для работы с текстом.
о Логический тип позволяет использовать понятия истинности (True) и ложности (False) для оценки выражений.
о Коллекции, такие как списки, кортежи, множества и словари, используются для хранения наборов данных. Строки тоже можно отнести к коллекциям, так как они хранят наборы символов.
Основы Python 46
Таблица 2 - Основные встроенные типы данных в Python			
Название	Обозначение	Определение	Пример
Целые числа	int	Натуральные числа, числа противоположные им и ноль	0, -14, 372
Числа с плавающей точкой	float	Вещественные числа в виде десятичной дроби	0.5. 1.2. 23.48
Строки	str	Упорядоченные последовательности символов, заключенные в кавычки	"Привет","Как дела?"
Логиче- ский тип	bool	Представляет истинность или ложность	True, False
Списки	list	Упорядоченные изменяемые последовательности элементов.	[1, "Яблоко", "Груша"]
Кортежи	tuple	Упорядоченные неизменяемые последовательности элементов	("Солнце", "Луна")
Словари	diet	Упорядоченные (с версии Python 3.7) последовательности пар «ключ-значение»	{"Имя": "Александр", "Фамилия": "Невский", "Возраст": 43}
Множества	set	Неупорядоченные коллекции уникальных элементов	{"Красный", "Белый", "Синий"}
Также существует специальный тип данных NoneType. предназначенный для обозначения отсутствия какою-либо значения. Обычно он представлен единст венным объектом - None.
Использование None распространено в ситуациях, когда требуется явно показать, что переменная ничего не содержит или функция ничего не возвращает.
Аннотации типов
Несмотря на то, что Python является языком с неявной типизацией, при которой тип переменной определяется во время выполнения программы,
Основы Python 47
существует способ явно указать ожидаемые типы переменных, называемый аннотацией ТИПОВ.
Аннотации стали частью стандарта языка начиная с версии Python 3.5 и позволяют указать тип переменной через двоеточие после её имени: имя_переменной: тип_переменной
Например, мы можем явно указать, что переменная name ожидается строкового типа, а переменная grades - это список:
name: str = "Бурундуков Алексей”
grades: list = [5, 4, 5, 5]
Аннотации не влияют на выполнение программы, но значительно улучшают читаемость и помогают редакторам кода подсказывать правильные типы и предупреждать об ошибках. Чаще всего аннотация типов используется при указании типов параметров функций и служат дополнительной документацией.
Изменяемые и неизменяемые типы данных
В зависимости от возможности изменения значения объекта после его создания, все типы данных в Python делятся на неизменяемые и изменяемые.
•	Числа (int, float)
•	Строки (str)
•	Логический тип (bool)
•	Кортежи (tuple)
•	Списки (list)
•	Множества (set)
•	Словари (diet)
Рисунок 13. Тины данных по изменяемости
Объекты неизменяемого типа (числа, строки, кортежи) не могут изменять своё содержимое после создания. Если значение переменной изменяется, то Python создаёт новый объект, а ссылку на старый объект удаляет сборщик мусора - механизм, который следит за объектами в памяти и автоматически освобождает место, котда они больше не используются.
Например, создадим переменную числового типа:
П = 10
Основы Python 48
print(id(n))
# Вывод: 140717977647832
После этого прибавим к этой же переменной другое число с помощью оператора сложения с присваиванием:
п += 2 # Эквивалентно п = п + 2
pnint(id(n))
#	Вывод: 140717977647896
Как видите, идентификатор изменился, так как числа являются неизменяемым типом данных. После изменения значения переменной п в памяти был создан новый объект со значением 12, на который теперь указывает переменная п.
Память компьютера
10
п
>140717977647832
Изменение значения переменной п
п
Память компьютера
>140717977647896
Рисунок 14. Изменение значения переменной целочисленного типа
Иа целое число 10 больше нет ссылок, поэтому он будет удален сборщиком мусора.
Объекты изменяемо! о типа (списки, множества, словари), наоборот, могут изменять своё содержимое без создания нового объекта и без изменения своего идентификатора в памяти.
Создадим список со школьными предметами:
subjects = [‘Математика , "Информатика", "Физика ] print(id(subjects))
#	Вывод: 2076557039872
Основы Python 49
После этого добавим в список новый элемент, сложив его с другим спис
ком:
subjects += ["Русский язык"] # Эквивалентно subjects = subjects + ["Русский язык"]
print(id(subjects))
#	Вывод: 2076557039872
Здесь идентификатор списка остался прежним, так как список - это изменяемый тип данных, и операция сложения с присваиванием изменила значение существующего объекта, а не создала новый. Поэтому переменная subjects указывает на один и тот же объект даже при его изменении.
Память компьютера
["Математика", Информатика", "Физика"]
*2076557039872
Изменение значения переменной subject
Память компьютера
("Математика", "Информатика", "Физика", "Русский язык"]
subjects
*2076557039872
Рисунок 15. Изменение значения переменной списочного типа
Определение типа переменной
Узнать тин данных переменной позволяет функция type().
Функция
Описание
Параметры
Возвращаемое значение
type(object)
Возвращает тип данных, к которому принадлежит объект
• object - объект, тип которого требуется определить
Тип данных объекта
Основы Python 50
Например, для строки данная функция вернёт <class ' str' >, а для списка - cclass 'list' >:
mouse = "Микки Маус" # Строка
print(type(mouse))
#	Вывод: <cLass 'str‘>
toys = ["Кукла", "Машинка", "Утечка'] # Список
print(type(toys))
#	Вывод: <с Lass 'List‘>
Сейчас мы называем целые числа, строки или списки типами данных. Но позже вы узнаете, что на самом деле за каждым типом данных скрывается понятие «класс», поэтому функция type() возвращает именно cclass 'название_типа'>. Класс это шаблон, по которому создаются объекты. Поэтому каждый тип данных соответствует своему классу, и именно классы задают правила поведения для каждого объекта этого типа.
И если функция type() возвращает тип переменной, то функция isin-stance() позволяет проверить принадлежность переменной определённому типу. При этом в зависимости от результата проверки данная функция возвращает одно из значений логического типа данных - True (с англ. - истина) или False (с англ. - ложь).
Функция	isinstance(object, type)
Описание	Возвращает True, если объект object принадлежит типу type, иначе - False * object - объект, который проверяется на принадлеж-
Параметры	ность типу гуре • type - тип, на принадлежность к которому проверяется object
Возвращаемое значение	True или False
Основы Python 51
Например, если целочисленную переменную проверять на принадлежность к типу int (целые числа), то данная функция вернёт True, а если к типу str (строки)-то False:
х = 10
print(isinstance(x, int))
#	Вывод: True
print(isinstance(x, str))
#	Вывод: Fatse
Функция isinstance() особенно полезна, когда нужно проверить, является ли объект экземпляром определенного типа перед выполнением каких-либо специфичных для этого типа операций.
Примеры
Пример 1. У правление данными в системе учета товаров
Система учета товаров для магазина хранит в переменных разные характеристики товаров: название, количество, цена и доступность, а аннотации типов показывают типы данных каждой из переменных:
#	Название товара (строка)
product_name: str = "Dell Vostro 3520"
#	Количество на складе (целое число)
quantity_in_stock: int = 50
#	Доступность товара (логический тип)
is_available: bool = True
ft Накопители данных (список)
data_storage: list = ["SSD И.2 PCIe 512 Гб", "Свободный слот 2.5 SATA"]
ft Количество USB разъёмов (словарь)
connectors: diet = {"USB 2.0": 1, "USB 3.0": 2}
Пример 2. Заказ в интернст-маз азине
Статус оплаты заказа в интернет-магазине может быть как оплачен, то есть соответствовать логическому значению True, так и не оплачен - False. Но по умолчанию он должен соответствовать значению None типа NoneType. В таком случае аннотации типов позволяют указывать несколько допустимых типов с помощью оператора | (логическое «или»):
#	Статус оплаты заказа (логическое значение или None)
order_status: bool I None = None
ft Номер заказаj если он уже создан (целое число или None)
order_id: int | None = None
ft Стоимость заказа (вещественное число или None)
order_value: float | None = None
Основы Python 52
#	Товары в заказе (список или None) items: list I None = None
#	Желаемая дата доставки (строка или None) deliverydate: str | None = None
И ioi и
J 1 iid данных переменной определяет, какие виды значений она может хранить и какие операции над этими значениями можно выполнять
S Типизация определяет как язык программирования работает с различными типами данных.
Сильная типизация (Python) требует преобразование типов для несовместимых операций, а при слабой типизации (JavaScript) типы преобразуются автоматически.
Неявная типизация (Python) не требует указание типа при объявлении переменной, а явная типизация (C++), наоборот, требует указывать тип данных переменной.
Динамическая типизация (Python) позволяет изменять гип данных переменной во время выполнения кода, а при статической типизации (C++) тип переменной объявляется при компиляции и не меняется.
J Аннотации типов позволяют явно указывать ожидаемые типы переменных и не влияют на выполнение программы.
✓ К основным встроенным типам данным относятся целые числа (int) и числа с плавающей точкой (float), логические значения (bool: True и False), специальный тип NoneType (объект None), а также коллекции: строки (str), кортежи (tuple), списки (list), множества (set) и словари (diet). Неизменяемые типы (числа, строки, кортежи) при изменении создают новый объект с новым идентификатором. Изменяемые типы (списки, множества, словари), наоборот, изменяют своё содержимое без создания ново! о объект а.
Функция type() возвращает тип данных объекта.
Функция isinstance() проверяет принадлежность объекта определённому типу данных.
Задания для самопроверки
1	Чем отличается сильная типизация от слабой?
Основы Python 53
2.	Что понимается под динамической типизацией в Python?
3.	Используйте аннотации типов и создайте переменную author со значением "Сьюзен Коллинз", переменную book_titles со значением ["Голодные игры"., "The Hunger Games"] и переменную year со значением 2010
4.	При изменении значения переменной числового типа будет ли изменён её идентификатор?
5.	Определите типы следующих переменных:
email = "admin@mail.ru" snow = False clothes = ("Брюки', 'Рубашка", "Галстук") points = { х", "у", "z"} page = 125 meal = { "Завтрак , "Обед1 , "Ужин1 } temperature = {"01.01.2024*: -25, "02.01.2024": -26, "03.01.2024": -23}
23 ЧИСЛА
Числовые типы данных являются основой многих вычислительных задач. В Python представлены целые, дробные, называемые числами с плавающей точкой, и комплексные числа, с которыми можно выполнять широкий спектр арифметических операций и использовать разнообразные математические функции.
Целые числа
Целые числа в Python представлены типом int (от англ, integer- целое число). Они широко используются в математике и программировании для представления количественных величин, порядковых номеров и многого другого.
Создать переменную целочисленного типа очень просто: достаточно присвоить ей целое числовое значение: students = 4и age = 29
Системы счисления
По умолчанию целые числа в Python интерпретируются как десятичные (используются цифры 0-9). Однако с помощью специальных префиксов можно задавать целые числа в других системах счисления.
Основы Python 54
Таблица 3 - Различные системы счисления в Python
Система счисления	Префикс	Алфавит	Функция перевода из десятичной системы счисления	Пример
Двоичная	0Ь	Цифры 0 и 1	bin(x)	0Ы000111, 0Ь0011, 0Ы101
Восьме- ричная	00	Цифры от 0 до 7	oct(x)	00107, 001333, 0064
Шестнадцатеричная	0Х	Цифры от 0 до 9 и буквы от А до F	hex(x)	0X47. 0ХА37, 0XB3
Используем специальные префиксы и создадим целые числа в двоичной, восьмеричной и шестнадцатеричной системах счисления:
binary_number = 0bl0Wl octal_nuinber = 00173 hexadecimal_ninnber = 0хА9
Л теперь воспользуемся функциями перевода в двоичную, восьмеричную и шестнадцатеричную системы счисления для преобразования числа 24: print(bin(24)) # Вывод: 0Ы1000 print(oct(24)) # Вывод: 0о30 print(hex(24)) # Вывод: 0x18
При этом функции bin(), oct() иЬех() возвращают строки, которые начинаются с одного из префиксов, обозначающих систему счисления (0Ь, 0о, 0х): print(type(bin(42))) # Вывод: <class 'str'>
Преобразование в целое число
В целое число можно преобразовать как строку, содержащую число, так и число с плавающей точкой, то есть вещественное число с дробной частью. Для этого в Python предназначена функция int().
Основы Python 55
Функция	int(x, base=10)
Описание	Возвращает целое число, полученное из числа или строки X • х - число или строка, которую нужно преобразовать в целое число
Параметры	Необязательные параметры: • base - основание системы счисления, в которой записано число в ороке х, по умолчанию base=10
Во тврашаемое значение	Целое число
Отсутствие пробелов в base=10 не является ошибкой или опечаткой. Согласно РЕР 8, при присваивании значения параметру функции не следует ставить пробелы до и после знака =. Поэтому если base является параметром функции, пробелы не ставятся, но если вы создаёте переменную с именем base, то вокруг оператора присваивания должно быть по одному пробелу.
Чаще всего в целые числа требуется преобразовывать строки, так как число в виде строки не может участвовать в арифметических операциях:
number = int( '224') print(number + 1) # Вывод: 225 print(type(number)) # Вывод: <cLass 'int'>
Если строку нельзя преобразовать в целое число, то это приведёт к ошибке ValueError:
number - int( 'Число 457")
#	Ошибка: VaLueError: invalid Literal for int() with base 10: 'Число 457'
При этом знак «минус» у отрицательных чисел обрабатывается корректно: number = int( '-65") print(type(number)) # Вывод: «class 'int’>
Основы Python 56
Если строка содержит число в другой системе счисления, необходимо указывать её основание с помощью параметра base. Однако строка с таким числом не должна содержать префикс системы счисления:
print(int( 11101001", base=2))
Л вывод: 233
print(int( 723”, base=8))
#	Вывод: 467
print(int( А37", base=16)) # вывод: 2615
Если строка с числом содержит префикс (например, 0Ь101), то параметр base не указывается, иначе это приведет к ошибке ValueError:
print(int(0bJ00101))
#	Вывод: 37 print(int(0o272)) # Вывод: 186 print (int(0xl02‘-)) # Вывод: 4139
Также в целое число можно преобразовать число с плавающей точкой. В таком случае дробная часть просто отбрасывается (без округления): print(int(l. 2)) # Вывод: 1 print(int(72.9)) # Вывод: 72
Heoi раниченность диапазона целых чисел
Одной из интересных особенностей Python является то, что диапазон целых чисел практически не ограничен и зависит только от объема доступной оперативной памяти вашего компьютера. Это означает, что вы можете работать с очень большими целыми числами без опасения переполнения, которое встречается в некоторых других языках программирования: п = 78446744073709551615
Для улучшения читаемости больших чисел в коле можно использовать символ подчеркивания (_) в качестве разделителя тысяч (или любой другой группы разрядов). Python игнорирует эти подчеркивания при интерпретации числа1
п = 78_446_744_073_709_551_615
print(n)
#	Вывод: 78446744073709551615
Основы Python 57
Числа с плавающей точкой
Числа с плавающей точкой в Python - это вещественные числа, имеющие дробную часть. Они представлены типом данных float (от англ, floating-point arithmetic - арифметика с плавающей точкой).
Создать переменную типа float можно, присвоив ей число с десятичной точкой:
score = 0.54
Даже целое число можно представить в виде числа с плавающей точкой, добавив значимый ноль после точки:
weight = 68.0
Такой ноль не влияет на значение числа, но указывает на определенную точность его представления.
Плавающая точка
Название «число с плавающей точкой» связано с особым способом хранения дробных значений в памяти компьютера. В отличие от целых чисел, где позиция каждой цифры фиксирована (например, в числе 123, цифра 2 всегда означает число 20), в числах с плавающей точкой десятичная точка может «плавать» или перемещаться.
Представьте, что вы хотите записать очень большое число, например, приблизительную скорость света в метрах в секунду (300 000 000), или очень маленькое число, например, заряд электрона в кулонах (0.00000000000000000016).
Если бы компьютер хранил эти числа так, как мы их пишем, потребовалось бы очень много места для нулей. Вместо этого, числа с плавающей точкой хранятся в виде двух основных компонентов;
о Мантисса - это значащая часть числа, содержит само число без учета его порядка (количества нулей).
о Порядок - это число, которое указывает, насколько сильно нужно «сдвинуть» десятичную точку в мантиссе. По сути, это степень числа 10 (или 2 для компьютеров), на которую умножается мантисса.
Тогда число 300 000 000 может быть записано как 3 х Ю8, а число 0.00000000000000000016 как 1.6 х Ю19, где числа 3 и 1.6 - мантиссы, а числа 8 и -19 - порядки. Такая запись числа называют экспоненциальной, а порядок или
Основы Python 58
степень числа 10 называют экспонентой
Однако при неизменной мантиссе, например. 3. можно представить и другие числа - 0.0003 как 3 х 10'4 или 3000000 как 3 х 10". То есть «плавающая точка» означает, что положение десятичной точки не фиксировано, а определяется порядком. Такой подход позволяет компьютерам эффективно хранить закис числа и выполнять вычисления над ними.
Преобразование в число с плавающей точкой
Как и в целое число, в число с плавающей точкой можно преобразовать строку, содержащую число, или целое число. Для этого предназначена функция float().
Функция	float (х)
Описание
Параметры
Возвращает число с плавающей точкой, полученное из числа или строки х
• х - число или строка, которую нужно преобразовать в число с плавающей точкой
Возвращаемое значение
Число с плавающей точкой
Переведём несколько чисел из строкового типа в тип числа с плавающей точкой:
print(float("2.57")) # Вывод: 2.57 print(float(“-12.5679")) # Вывод: -12.5679
Целые числа так же можно преобразовать в вещественные: print(float(2)) # Вывод: 2.0 print(float(10)) # Вывод: 10.0
В таком случае к числу просто будет добавлен значимый ноль.
Основы Python 59
Экспоненциальная запись чисел с плавающей точкой
В Python числа с плавающей точкой можно представлять в виде экспоненциальной записи. Для этого используется символ экспоненты е (или Е), означающий «умножить на 10 в степени».
Например, рассмотрим следующие переменные:
point = 0.0000000014 radius = 2100000000
Экспоненциальная запись сделает их значения более наглядными, так как позволит сразу оценить размер числа и количество нулей:
point = 1.4е-9 radius = 2.1е9
Такая запись чисел в Python включает в себя следующие части:
о Мантисса - десятичное число, например, 1.4 или 2.1.
о Символ экспоненты - е или Е.
о Порядок или показатель степени - целое число, указывающее степень, в которую возводится число 10, например, 9 или -9.
Поэтому число 1.4е-9 означает 1.4 х 10 ч, а число 2.1е9 - это 2.1 х Ю9.
Точность вычислений с плавающей точкой
Хотя числа с плавающей точкой удобны для представления широкого диапазона значений, они имеют свою специфику, связанную с точностью. Из-за того, что компьютеры хранят эти числа в двоичной системе (основание 2), а не в десятичной (основание 10), не все десятичные дроби могут быть представлены с достаточной точностью.
Например, такие десятичные дроби как 0.1, 0.2 или 0.3 не имеют точною двоичного представления. Это может привести к небольшим, но иногда значимым ошибкам округления при выполнении арифметических операций:
print(0.1 + 0.2)
# Вывод: 0.30000000000000004
Здесь мы ожидаем увидеть значение 0.3. но из-за особенностей вычислений с плавающей точкой мы видим число 0.30000000000000004.
Для большинства повседневных вычислений эти незначительные отклонения не критичны, и далее мы научимся округлять такие числа. Однако в
Основы Python 60
финансовых или научных расчетах, где требуется абсолютная точность, могут потребоваться специальные библиотеки, например, decimal, позволяющие работать с такими числами с заданной точностью.
Приморье
Пример I. Управление данными в системе учета товаров
В системе инвентаризации товары имеют уникальные идентификаторы, которые должны быть переведены в различные форматы, например, для печати штрихкода или этикеток:
#	ID товара в десятичной системе счисления product_id_decimal = 65535
print( Десятичный код товара; , product_id_decimal)
#	Переведём ID товара в двоичный формат для штрихкода
binary_code_str = bin(product_id_decimal) print( Дюичныи код товара: , binary_code_str)
#	Переведём ID товара в шестнадцатеричный формат для печати этикеток hex_representation = hex(product_id_decimal)
print(f“Шестнадцатеричный код товара:", hex_representation)
Вывод:
Десятичный код товара: 65535
Двоичный код товара: Obllllllllllllllll
Шестнадцатеричный код товара: Oxffff
Очень большой инвентарный номер: 987654321098765432109876543210
Пример 2. Анализ научных данных
В научных исследованиях часто приходится работать с очень большими или очень маленькими числами, т акими как расстояние до звезд или размеры молекул. которые более наглядны в экспоненциальной записи:
#	Приблизительное расстояние до Проксима Центавра в метрах
star_distance = 4.011е16 # 4.011 х 10Л16 метров
print( Расстояние до Проксимы Центавра (м):", star_distance)
#	Приблизительный диаметр молекулы воды в метрах
water_diameter = 2.75е-10 # 2.75 х 10Л-10 метров print( Диаметр молекулы воды (м): , water_diaineter)
#	Преобразуем числа с плавающей точкой в целочисленный тип
stardistanceint = int(star_distance) # Дробная часть отбрасывается
print( Расстояние до Проксимы Центавра (м, целое число) , star_distance_int) water_diameter_int = int(water_diameter)
Основы Python 61
print('Диаметр молекулы воды (м, целое число):", water_diameter_int)
Вывод:
Расстояние до Проксимы Центавра (м): 4.011е+16
Диаметр молекулы воды (м): 2.75е-10
Расстояние до Проксимы Центавра (м, целое число): 40110000000000000
Диаметр молекулы воды (м, целое число): 0
Июги
✓ Python поддерживает различные числовые типы данных: целые числа, числа с плавающей точкой (дробные) и комплексные числа.
S Целые числа по умолчанию представлены в десятичной системе счисления, но их можно задавать в двоичной, восьмеричной и шестнадцатеричной системах счисления. Функции bin(), oct() и hex() преобразуют десятичные числа в строки соответствующей системы.
J Диапазон целых чисел в Python практически neoi раничен и зависит от доступной памяти.
✓ Числа с плавающей точкой хранятся в компьютере в виде мантиссы и порядка. Май тисса представляет собой значение числа, а порядок - степень, в которую возводится основание системы счисления (обычно 2 для компьютеров).
Числа с плавающей точкой могут испытывать проблемы с точностью из-за способа их представления в компьютере.
Числа с плавающей точкой можно записывать в экспоненциальной форме с помощью символа е (или Е), означающего «умножить на 10 в степени». Функция int() преобразует другое число или строку в целое число.
J Функция float () преобразует другое число или строку в число с плавающей точкой.
Задания для самопроверки
1.	Каким типом данных представлены результаты перевода числа из десятичной системы счисления в другую с помощью функций bin(). oct() и hex()?
2.	Что будет выведено на экран в результате выполнения данного кода? print(int(i2.i)) print(int(9.99))
3.	Какому типу данных соответствуют дробные числа?
Основы Python 62
4.	Представьте значения следующих переменных в экспоненциальной за
писи:
X = 1200ИЙ0000н
у = 0.00000071
5.	Что делает функция -Float()?
2.	4 АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ
В Python числа поддерживают широкий спектр арифметических операций, которые являются основой для выполнения математических расчетов. Возможны как самые простые операции: сложение, вычитание, умножение и деление, так и более специфичные: получение остатка от деления, целочисленное деление и возведение в степень.
Приори-	Таблица 4 - Арифметические операции			
	Опера-	Описание	При-	Резуль-
те!	тор		мер	тат
1	**	Возведение левого операнда в сте-	3 *¥ 2	9
		пень правого	2**з	8
2	%	Получение остатка от деления левого	7 % 2	1
		операнда на правый	13 % 5	3
2	И	Целочисленное деление левого опе-	5 // 2	2
		ранда на правый	70 И 8	8
2	/	Деление левого операнда на правый	12/5	2.4
			75/6	12.5
2	*	Умножение левого операнда на пра-	10 * 5	50
		вый	3*6	18
3	-	Вычитание правого операнда из ле-	87 - 5	82
		вого	54 - 10	44
3	+	Сложение правого и левого операнда	12 + 6	18
			76+1	77
Арифметические операции в Python выполняются как в математике, то есть сначала выполняется возведение в степень (**), затем - умножение (*) и деление (/, // и %), и в последнюю очередь - сложение (+) и вычитание (-). При этом операторы, имеющие одинаковый приоритет, выполняются слева направо.
Основы Python 63
Однако мы можем явно определить порядок выполнения операций используются круглые скобки Выражения в скобках всегда вычисляются первыми: print(2 + 2*2) # Вывод: Ь print((2 + 2) * 2) # Вывод: 8
Особенности операции деления
При обычном делении (/) результатом всегда является число с плавающей точкой (тип float), даже если деление происходит нацело:
p^int(6 / 3) # Вывод: 2.0
Но при целочисленном делении (//) возвращается целая часть отделения без дробного остатка, поэтому результатом будет целое число (тип int):
print(7 // 2) # Вывод: 3
Также Python позволяет сразу получить остаток отделения: print(7 % 2) # Вывод: 1
Остаток от деления - это число, которое остаётся после того, как одно число поделили нацело на другое. Так, когда мы нацело поделили 7 на 2, то получили число 3. однако ещё осталось число 1, так как 7 — 3 • 2 = 1. То есть этот оставшийся после деления «кусочек» делимого числа и является остатком от деления.
Нахождение остатка от деления часто используется для проверки кратности числа или определения четности/нечетности:
print(21 % 2) # Число нечётное, так как остаток не равен 0 # Вывод: 1
print(40 % 2) # Число четное, так как остаток равен 0 it Вывод: 0
Арифметические операторы присваивания
Python позволяет не только выполнить арифметическую операцию над переменной, но и сразу же присвоить ей результат этой операции. Для этого предназначены расширенные операторы присваивания
Основы Python 64
Таблица 5 - Арифметические операции с присваиванием
Опе- ратор	Описание	При- мер	Ан ал от		
+=	Сложение правого и левого операнда, и присваивание результата левому операнду	х += 2	X	= X	+ 2
— =	Вычитание правого операнда из левого, и присваивание результата левому операнду	х -= 2	X	= X	- 2
* =	Умножение левого операнда на правый, и присваивание результата левому операнду	х *= 2	X	= X	* 2
/=	Деление левого операнда на правый, и присваивание результата левому операнду	х /= 2	X	- X	/ 2
//=	Целочисленное деление левого операнда на правый, и присваивание результата левому операнду	х //= 2	X	= X	// 2
0/_ /о—	Получение остатка от деления левого операнда на правый, и присваивание результата левому операнду	х %= 2	X	= X	% 2
** —	Возведение левого операнда в степень правого, и присваивание результата левому операнду	х **= 2	X	- X	** 2
Такие операторы выполняют операцию между правым и левым операндами и сразу присваивают результат левому операнду, то есть перезаписывают его значение, например: numberl = 10 numberl += 5 # Эквивалентно numberl = numberl + 5 print(numberl) # вывод: 15
number? = 18
number? //= 10 # Эквивалентно number? = number? // 10 print(number?) # Вывод: 1
Расширенные операторы присваивания могут использоваться только с переменными. которые уже были определены ранее. Попытка использовать их с несозданными переменными приведет к ошибке NameError.' age += 1
# Ошибка: NameError: name 'age' is not defined
Основы Python 65
Примеры
Пример 1. Расчёт количества рулонов обоев
Интернет-магазин строительных материалов позволяет рассчитать необходимое количество рулонов обоев, зная длину, ширину и высоту комнаты, а также
длину и ширину рулона:
#	Размеры комнаты 8 метрах
width_room =4.5 # Ширина комнаты (м) length_room =6	# Длина комнаты (м)
height = 2.7	# Высота комнаты (м)
#	Размеры рулона обоев wallpaperwidth =0.53 # Ширина рулона (м) wallpaper_length =10	# Длина рулона (м)
#	Периметр комнаты (Р = 2 * (а + Ь)) perimeter = 2 * (width_room + length_room)
#	Количество полос обоев (делим периметр на ширину рулона) strips = perimeter / wallpaper_width
#	Количество полос из одного рулона (олина рулона / высота комнаты) strips_per_roll = wallpaper_length // height # Целочисленное деление
#	Итоговое количество рулонов (округляем в большую сторону)
rolls = strips I strips_per_roll
print( Нужно рулона- , int(rolls + 0.5))
Для упрощения расчётов в данном примере не учитываются окна и двери. Однако обои продаются только целыми рулонами, поэтому здесь к результату прибавляется 0.5 для округления к большему ближайшему целому, однако позже мы познакомимся со специальными функциями округления.
Вывод:
Нужно рулонон: 14
Пример 2. Расчёт среднего балла студента
Электронный журнал позволяет посчитать средний балл студента по нескольким предметам и корректно округлить его для отображения в отчете:
#	Оценки студента по разным предметам
math_grade = 4 # Оценка по математике
physics_grade = 5 # Оценка по физике
literature_grade = 3 # Оценки по литературе
history grade = 3 # Оценка по истории
Основы Python 66
# Количество предметов num_subjects = 4
#	Вычисляем сумму баллов
total_grades_sum = math_grade + physics_grade + literature_grade + his-tory_grade
#	Вычисляем средний оалл (обычное деление, результат float)
average_grade = total_grades_sum / num_subjects
print(f'Средний балл: {average_grade }")
Вывод:
Средний балл: 3.75
Hioi и
S Python поддерживает основные арифметические операции: сложение (+), вычитание (-), умножение (*), деление (/). возведение в степень (**), целочисленное деление (//) и получение остатка от деления (%).
J Приоритет арифметических операций соответствует математическому, но порядок действий можно изменить с помощью круглых скобок.
J Обычное деление (/) возвращает число с плавающей точкой (float), даже если результат целое число.
•S Целочисленное деление (//) возвращает целую часть от деления (int).
J Арифметические операторы присваивания (например. +=, -=, *=) совмещают операцию и присваивание (х += 5 эквивалентно х = х + 5). Они работают только с уже созданными переменными.
Задания для самопроверки
1.	Опишите приоритет выполнения арифметических операций.
2.	Как происходит целочисленное деление в Python?
3.	Что будет выведено на экран в результате выполнения данного кода? print(13 % 2 + 1) print(2 ** 3 // 3) print(10 / 2 ** 2) print(-25 *4/5) print((2 + 3) ** 2) print(2 + 3 ** 2)
4.	Что будет выведено на экран в результате выполнения данного кода?
п! = 10 % з + 2
Основы Python 67
rl /= 3 print(nl)
n2 = 3 ** 2
n2 -= 1
print(n2)
n3 = 21 // 4
n3 += 2
print(n3)
n4 = 12 - 4 ** 2
пД *= 3
print(n4)
п5 = 30 / 2
п5 %= 2
print(n5)
2.	5 МАТЕМАТИЧЕСКИЕ ФУНКЦИИ
Помимо базовых арифметических операций, Python предлагает набор встроенных функций, которые помогают работать с числами - это функция abs(). возвращающая модуль числа, функция round() для округления чисел, и функция pow(). эквивалентная оператору возведения в степень.
Абсолютное значение чисел
Функция abs() (от англ, absolute — абсолютный) позволяет получить абсолютное значение числа, то есть его модуль. В математике модуль представляет собой расстояние на числовой прямой о г этого числа до 0. И так как расстояние не может быть отрицательным, то можно сказать, что модулем числа является это же число, но без учёта знака
Функция	abs(number)
Описание	Возвращает абсолютное значение числа number
Параметры	• number - число, абсолютное значение которого вычисляется
Возвращаемое значение	Абсолютное значение числа
Основы Python 68
Функция abs() очень проста в использовании и в любом случае возвращает положительное значение этого же числа: print(abs(5)) # вывод- 5 print(abs(-lh)) # Вывод: 10 print(abs(-3.14)) it Вывод: 3.14
Округление чисел
Результат арифметической операции, например, деления, часто нуждается в округлении: print(10 / 3) # Вывод: 3.3333333333333333
Для этого в Python предназначена функция round().
Функция	round(number, ndigits=None)
Описание	Возвращает число number, округлённое до ndigits знаков после запятой • number - округляемое число
Параметры	Необязательные параметры: • ndigits - количество знаков после запятой, по умолчанию ndigits^None
Во зврашаемое значение	Число
Использование параметра ndigits позволяет округлить результат деления до указанного количества десятичных знаков: result =10/3 print(round(result, 2)) it Вывод: 3.33
Иначе функция round () округляет число до ближайшего целого: print(round(3.33)) it Вывод: 3
Для случаев, когда число ровно посередине между двумя ближайшими значениями (например. 3.5 или 2.5), в Python действует банковское округление. То
Основы Python 69
есть значение округляется в сторону ближайшего чётного числа. Поэтому число 2.5 будет округлено до меньшего числа 2:
print(round(2.5))
#	Вывод: 2
Но число 3 будет округлено до большего числа 4:
print(round(3.5))
#	Вывод: 4
Такой метод округления может показаться странным, ведь в математике мы округляем число в меньшую сторону, если после запятой присутствуют цифры от 1 до 4, а в большую - цифры от 5 до 9. Однако банковское округление позволяет бороться с накоплением ошибки при совершении операций с округлёнными числами:
#	Запишем исходное выражение
expression = 1.5 + 2.5 + 3.5 + 4.5 print(expression) # Вывод: 12
4 Используем математическое округление
math_round =2+3+4+5 print(math_round)
#	Вывод: 14
#	Используем банковское округление
bank_round =2+2+4+4 print(bank_round) # Вывод: 12
Здесь математическое округление дало погрешность 2, в то время как банковское округление вовсе позволило её избежать.
Возведение в степень
Для возведения чисел в степень можно использовать оператор **, как мы делали это ранее. Однако в Python существует специальная встроенная функция pow() (от англ, power — степень), которая выполняет эту же задачу. Несмотря на то, что для возведения в степень оператор ** является более привычным, функция pow() может быть полезна, когда нужно использовать её дополнительные возможности.
Основы Python 70
Функция
Описание
Параметры
Возвращаемое значение
pow(number, exp, mode-None)
Возвращает число number, возведённое в степень exp
•	number - число, возводимое в степень
•	ехр - степень, в которую возводится число
Необязательные параметры:
•	mod - число, остаток от деления на которое следует найти, по умолчанию mode=None
Число
Использование pow() с двумя аргументами аналогично оператору **: print(pow(2, з)) # Вывод: 8 print(2 ** 3) # Вывод: 8
Однако параметр mod позволяет объединить возведение в степень с нахождением остатка от деления:
print (poi«(2, з, з))
#	Вывод: 2 print((2 ** 3) % 3) # Вывод: 2
Нахождение остатка от деление по-другому называют делением по модулю. Это находит своё применение в математике, например, числа часто сравнивают по модулю, то есть определяют, имеют ли эти числа одинаковый остаток при делении на один и тот же модуль.
Примеры
Пример I. Расстояние до цели в из ре
В игре нужно определить, насколько далеко объект находится от цели:
#	Координаты игрока и цели по оси х player posit ion = -5 target_position = 3
#	Модуль разности оасстояний distance = abs(player_position - target_position) print(f Игр >к находится на расстоянии {distance} шагов от цели")
Основы Python 71
Вывод:
Игрок находится на расстоянии 8 шагов от цели
Пример 2. Конвертер валют
Конвертер валют на сайте пункта обмена переводит рубли в доллары по курсу 78.72 рубля за 1 доллар, а также учитывает комиссию в 1.5 %:
rub = ЮЭ0 # Рубли для обмена exchange_rate =78.72 # Курс commission =1.5 # Комиссия в %
#	Конвертация с учётом комиссии
usd = (rub I exchange_rate) * (1 - commission / 100)
print("MToro:", round(usd, 2), "долларов") # Округляем до центов
Вывод:
Итого: 12.51 долларов
Пример 3. Создание ключа доступа
Программа создаёт временный и уникальный ключ для доступа к веб-сервису. Ключ создаётся на основе трёх уникальных чисел и общих параметров:
#	Три уникальных и важных числа
data_l = 123
data_2 = 456
data_3 = 789
#	Общие параметры для каждого из чисел prime_base = 31 # Простое число, основание modulus = 10”9 + 7 # Большой простои модуль # Вычисление	ключа
secret_key	=	(
data_l	*	pow(prime_base,	2,	modulus)	+
data_2	*	pow(prime_base,	1,	modulus)	+
data_3	*	pow(prime_base,	0,	modulus)
) % modulus
print(f"Уникальный ключ: {secret_key}")
Вывод:
Уникальный ключ: 133128
Итоги
J Функция abs() возвращает абсолютное значение (модуль) числа, то есть это же число, но без учёта знака.
Основы Python 72
J Функция round () округляет число до указанного количества десятичных знаков или до ближайшего целого.
Для чисел, находящихся ровно посередине (например. 2.5. 3.5), Python использует банковское округление - округление к ближайшему чётному числу, чтобы уменьшить накопление ошибок.
✓ Функция pov\/() эквивалентна оператору ** и возводит число в степень, но также позволяет сразу же поделить этот результат по модулю с помощью параме тра mod.
Задания для самопроверки
1.	Что будет выведено на экран в результате выполнения данного кода?
print(abs(-15.5))
print(abs(100))
print(abs(0))
2.	Используйте функцию round() и округлите числа 0.15865 и 32.23124 до трёх знаков после запятой, а числа -8.87231 и 14.5 - до ближайшего целого числа.
3.	Каким будет результат каждого вызова функции round() и почему?
value = 3.14159265
print(round(value, 2))
print(round(value, 4))
print(round(value, 0))
4.	Почему результаты round(2.5) и round(3.5) отличаются от того, что ожидается в традиционной математике?
5	Попробуйте выполнить все операции в уме и предсказать, какое значение будет выведено в конце:
initial_number = -2.75
#	Шаг 1: найти нодуль числа
stepl = abs(initial_number)
#	Шаг 2: Округлить результат
step2 = round(stepl)
#	Шаг 3: Возвести результат в третью степень
final_result = pow(step2, 3)
print(final_result)
6. В чём заключается разница между рош(5, 3) и pow(5, 3, 2)?
Основы Python 73
2.6 ОСНОВЫ РАБОТЫ СО СТРОКАМИ
Строки в Python представлены типом str (от англ, string - строка) и представляют собой упорядоченные последовательности символов, заключённые в кавычки. Строки используются для хранения и обработки текстовой информации Будь то имена, адреса, тексты книг или любые другие данные, которые можно представить в виде текста, в Python они обычно обрабатываются как строки.
Для создания строковой переменной достаточно заключить любой текст в одинарные (') или в двойные (") кавычки:
message = 'Что на ужин?'
reply_message = "" # Пустая строка - тоже строка
Длинные строки
Если строка очень длинная, то сё можно разбить на части с помощью тройных кавычек;
recipe = """Ингредиенты:
1-2 стол, вые ложки растительного масла или кусочек сливочного;
1 или несколько яиц;
соль - по вкусу..
print(recipe)
При выводе такой строки на экран в ней сохранятся все символы, включая переносы строк:
Ингредиенты:
1-2 столовые ложки растительного масла или кусочек сливочного;
1 или несколько яиц;
соль - по вкусу.
Избежать этого можно с помощью разбиения длинной строки на несколько частей и заключения их в круглые скобки:
info = ("Тест Тьюринга - эмпирический тест, идея которого была " "предложена Аланом Тьюрингом в статье «Вычислительные машины " "и разум», опубликованной в 1950 году в философском журнале" " Mind. Тьюринг задался целью определить, может ли машина " "мыслить") print(info)
Python автоматически объединит эти части в одну строку и в полученной строке переносов строк не будет:
Тест Тьюринга - эмпирический тест, идея которого была предложена Аланом Тьюрингом в статье «Вычислительные машины и разум», опубликованной в 1950 году в
Основы Python 74
философском журнале Mind. Тьюринг задался целью определить, может ли машина мыслить.
Обратите внимание, что запятые между строками в скобках ставить не нужно, иначе будет создан кортеж, а не строка.
Преобразование в строку
Практически для любого объекта в Python можно получить его строковое представление с помощью функции str(). Это означает, что мы можем преобразовать любые числа в строки.
Функция	str(obj)
Описание	Возвращает строку, полученную из объекта obj
Параметры • obj - объект, строку из которого требуется получить
Возвращаемое значение
Строка
Например, преобразуем в строку целое число:
number = 21
str_number = str(number)
print(str_number)
if Вывод: 21
При выводе на экран может быть не так очевидно, что переменная number хранит строку, однако мы можем убедиться в этом с помощью функции type(): print(type(str_number)) # Вывод: <cLass 'str‘>
Если функции str () передано арифметическое выражение, то сначала оно будет вычислено, и только затем преобразовано:
print(str(30 + 1))
ff Вывод: 31
Базовые операции со строками
Python предоставляет несколько базовых операций для работы со строками: конкатенацию (сложение) и повторение (умножение).
Основы Python 75
Конкатенация (сложение) строк
Операция конкатенации объединяет две строки в одну новую строку с помощью оператора сложения (+).
fist__name = "Бильбо"
last_name = ‘Бэггинс" print(fist_name +	+ last_name)
#	Вывод: Бильво Бэггинс
Как и при сложении чисел, допускается использовать оператор присваивания со сложением (+=). Тогда результат конкатенации строк будет сразу же присвоен переменной слева:
паше = "Фродо
last_name = "Бэггинс
name += last_name print(name) # Вывод: Фродовэгганс
При этом оператор += не изменяет исходную строку, а создаёт новый объект. который присваивает переменной паше. Это связано с неизменяемостью строк.
Конкатенация возможна только между двумя строками. Попытка сложить строку с числом приведет к ошибке ТуреЕггог. Чтобы избежать этого, число должно быть преобразовано в строку с помощью функции str()‘ amount = 3 print( Яблоки: " + str(amount)) # Вывод: ЯЬлоки: 3
Новiпрение (умножение) строк
Оператор умножения * при работе со строками выполняет операцию повторения. Он создаёт новую строку, которая является результатом многократного повторения исходной строки. Левый операнд должен быть ст рокой, а правый - целым числом, указывающим, сколько раз необходимо повторить строку: print("Kap-Kap" * 3) # Вывод. Кар-карКар-карКар-кар
Здесь также разрешается использование оператора присваивания с умножением (*=), который создаёт новую строку и присваивает её переменной слева: song = о jy" song *= 3 print(song) # Вывод: o-oyo-oyo-oy
Основы Python 76
Форматирование строк
Форматирование строк - это процесс создания новых строк путем вставки в них значений переменных или результатов выражений в определенном формате. Python предлагает несколько способов форматирования строк.
Для этого мы можем использовать уже знакомую нам конкатенацию строк: first_name = "Энакин” last_name = "Скайуокер" age = 26 print("Меня зовут ' + first_name + " " + last_name + ". Мне " + str(age) + лет. ”)
#	Вывод: Меня зовут Энакин Скайуокер. Мне 26 лет.
Однако, чем больше в такой строке переменных, гем сложнее она становится для чтения. Также необходимо следить за тем, чтобы все элементы были строками, при необходимости используя функцию str().
Одним из самых удобных и современных способов встраивания выражений в строку является использование f-строк (форматированных строковых литералов). Для того, чтобы создать f-строку, достаточно поставить префикс f перед открывающей кавычкой строки, а любые переменные или выражения Python заключить в фигурные скобки:
print(f"Меня зовут {first_name} {last_name). Мне {age} лет.') # Вывод: Меня зовут Энакин Скайуокер. Мне 26 лет.
Одним из преимуществ f-строк является то. что больше не нужно вручную преобразовывать типы данных в строки - Python сделает это автоматически внутри фигурных скобок. Кроме того, f-строки обычно работают быстрее, чем конкатенация с использованием оператора +.
Также внутри фигурных скобок в f-строках можно использоват ь любые допустимые выражения Python, включая арифметические операции и вызовы функций: п = 2 power = 8 print(-f' {n) в степени {power} равняется {п ** power} ) # Вывод: 2 в степени 8 равняется 256
Кроме этого, f-строки поддерживают спецификаторы формата, которые контролируют, как именно отображается значение внутри строки. Спецификатор формата следует после двоеточия внутри фигурных скобок.
Например, для дробного числа можно указать количество знаков после запятой:
Основы Python 77
pi = 3.1415926535
print(f Число Пи приблизительно равно {pi:.2f} ) # Вывод: Число Пи приблизительно равно 3.14
Здесь . 2f - спецификатор формата, который задает желаемое количество знаков после десятичной точки (в данном случае 2), а также указывает, что число должно быть отформатировано как число с плавающей точкой (f от float).
Примеры
Пример 1. Вывод на экран приветственного сообщения
Программа выводит на экран многострочное приветственное сообщение:
#	Используем тройные кавычки для сохранения переносов строк welcome_message_triple_qiiotes = """Добре пожалспать в программу! Пожалуйста, ознакомьтесь с правилами:
1.	Соблюдайте конфиденциальность.
2.	Будьте вежливы.
3.	Улыбнитесь.
Спасибо за сотрудничества!..
print(welcome_message_triple_quotes, end= \n\n )
# Объединяем строки в скобках для создания одной длинной строки welcome_message_parentheses = (
"Сегодняшний день - чистый лист бумаги, на котором "
"предстоит нарисовать самые яркие краски своей жизни. "
)
print(welcome_message_parentheses)
Вывод:
Добро пожаловать в программу!
Пожалуйста, ознакомьтесь с правилами:
1.	Соблюдайте конфиденциальность.
2.	Будьте вежливы.
3.	Улыбнитесь.
Спасибо за сотрудничество!
Сегодняшний день - чистый лист бумаги, на котором предстоит нарисовать самые яркие краски своей жизни.
Пример 2. С оздание отчёта о продажах
Программа выводит на экран краткий отчет о продажах, включающий в себя количество проданных товаров, общую выручку и долю каждого товара в выручке, округлённую до двух знаков после занятой, процентные значения.
#	Данные о продажах товара А и товара В
product_a_name = "Мышь беспроводная" # Название товара А
product_a_sales = 140 # Всего продано товара А
Основы Python 78
product_a_revenue = 13553.5 # Общая выручка по товару А product_b_name = ‘Клавиатура беспроводная" # Название товара В product_b_sales = 75 # Всего продано товара В productbrevenue = 28373.5 # Общая выручка по товару В
#	Общие показатели
total_sales = product_a_sales + pr0duct_b_sal.es # Всего продано total_revenue = product_a_revenue + product_b_revenue # Общая выручка
#	Расчет доли каждого товара в общей выручке в процентах product_a_share = (product_a_revenue I total revenue) * 100 product_b_share = (product_b_revenue I total_revenue) * lo0
print(f Общее количество проданных товаров: {total_sales} шт. ) print(f Общая выручка: {total_revenue} руб.\п’’)
print(f"Товар: {product_a_name}")
print(f" Придано: {product_a_sales} шт.') print(f Выручка; {product_a_revenue} руб. ) print(f" Доля в общей выручке: {product_a_share:.lf}% )
print(F'Topap: {product_b_name}")
print(F‘ Продано: {product_b_sales} шт.’)
print(f Выручка: {product_b_revenue} руб. )
print(f Доля в общей выручке: (product_b_share-.If} > )
Вывод:
Общее количество проданных товаров: 215 шт.
Общая выручка: 41927.О руб.
Товар: Мышь беспроводная
Продано: 140 шт.
Выручка: 13553.5 руб.
Доля в общей выручке: 32.3%
Товар: Клавиатура беспроводная
Продано: 75 шт.
Выручка: 28373.5 руб.
Доля в общей выручке: 67.7%
Итоги
J Строки - упорядоченные последовательности символов, заключённые в кавычки.
J Длинные строки можно разбивать на части с помощью тройных кавычек (сохраняются переносы строк) или круглых скобок (не сохраняются переносы строк)
S Функция str() преобразует практически любой объект (включая числа и
Основы Python 79
результаты выражений) в строку.
J Конкатенация (+) объединяет две или более строк в одну.
Повгпрение (*) создаёт новую строку путём многократного повторения исходной строки (строка * целое число).
S F-строки позволяют встраивать переменные или выражения в строку с помощью фигурных скобок.
F-строки поддерживают спецификаторы формата (например, {pi:.2f} для округления дробных чисел).
Задания для самопроверки
1.	Чем отличаются длинные строки, созданные с помощью тройных кавычек от строки, разделённой с помощью скобок?
2.	Преобразуйте число 23 в строку.
3.	Найдите и исправьте ошибку в данном коде:
price = 17
str_price = str(price)
print(price + " долларо! )
4.	Используйте оператор умножения (*) и создайте строку "УраУраУраУра-УраУраУра" из строки ‘'Ура".
5.	Что будет выведено на экран в результате выполнения данного кода? number! = 3 rumber2 = 5
print(f"Сумма чисел {number!} и {number2} равна {number! + number2}")
6.	Используйте спецификаторы формата и выведите на экран число Эйлера е = "2.7182818284" с гремя знаками после точки.
2.7 ЭКРАНИРОВАНИЕ И КОДИРОВАНИЕ СИМВОЛОВ
Программирование на Python требует внимания ко многим деталям, особенно когда речь идет о работе с текстом.
Управляющие символы
В Python некоторые последовательности символов, начинающиеся с
Основы Python 80
обратного слэша (\), интерпретируются особым образом Такие последовательности называют управляющими символами или escape-последовательностями Они необходимы для задания специфичных действий в тексте, таких как перенос строки, табуляция или возврат каретки.
Таблица 6 - Основные управляющие символы
Управляющий символ
\п (от англ, newline - новая строка)
\t (от англ, tab - табуляция)
\г (от англ, return - вернуться)
\ь (от англ, backspace - пробел назад)
Назначение
Перенос строки
Добавление табуляции (отступа) Возврат каретки в начало строки Удаление последнего символа
Символ переноса строки (\п) позволяет вывести на экран несколько строк текста без использования дополнительных вызовов функции print ():
poem = "Духовной жаждою томим,\пВ пустыне мрачной я влачился."
print(роет)
Здесь символ \п указывает Python, что после "Духовной жаждою томим," следует перейти на новую строку:
Духовной жаждою томим, В пустыне мрачной я влачился.
Символ добавления табуляции (\t) полезен, когда необходимо добавить отступ, например, для выравнивания столбцов при выводе таблицы:
countries = "СтранаНСт':лица\пРоссия\ТМосква\пКитай^Пекин"
print(countries)
При выводе такой строки на экран каждый столбец «Страна-Столица» отделен отступом:
Страна Столица
Россия Москва
Китай Пекин
Символ возврата каретки (\г) перемещает курсор в начало текущей строки. Следующий за этим символом текст перезаписывает существующий: print( Или вырубишь\гЧтс написано пером, не ьырубишь топором ) print('XoTfl если сообщение о-очень длинное, то вырубишь только часть\гЧтв написано пером, не вырубишь топором")
Здесь в первой строке часть до символа \г будет перезаписана полностью, а во второй - частично, так как она длиннее:
Основы Python 81
Что написано пером, не вырубишь топором
Что написано пером, не вырубишь топором вырубишь только часть
С имвол перевода каретки назад \Ь удаляет последний символ: print("123\b45‘) print('Ои ты\Ь\Ь\Ь, степь широкая!')
Подряд можно использовать несколько управляющих символов, поэтому во втором примере полностью удалена строка " ты":
1245
Ой, степь широкая!
Термин «кареткам широко применяется программистами для обозначения позиции текстового курсора, указывающей место ввода. Однако каретка и управляющие символы берут свое происхождение от механических пишущих машинок. Раньше кареткой называли подвижную часть машинки, которая определяла положение печатающего элемента. Клавиши вроде «Перевод строки» и «Возврат каретки» помогали операторам начинать новые строки и возвращаться к началу текущей строки соответственно. Со временем
эти термины перешли в компьютер, сохранив свое первоначальное функциональное предназначение.
Экранирование символов
Часто строка должна содержать специальный символ как свою часть, то есть без использования его специального значения. Рассмотрим простой случай, когда нужно сохранить путь к файлу в Windows:
path = "C:\Python projects\task.py’’
pr’int(path)
Результат вывода такой строки на экран может показаться неожиданным:
C:\Python projects ask.py
Появление отступа связано с тем, что последовательность символов \ и t B\task.py рассматривается Python как управляющий символ, обозначающий
Основы Python 82
добавление табуляции. Чтобы исправить эту ситуацию, нужно экранировать символ обратной косой черты, добавив перед ней ещё один обратный слэш (\): path = "C:\\Python projectsWtask.py" print(path)
Теперь путь выводится корректно, так как каждый символ \ был экранирован:
C:\Python projects\task.py
Экранирование позволяет указать интерпретатору, что конкретный символ следует воспринимать буквально, а не как служебный знак. Поэтому мы даже можем включить в строку кавычки того же тина, что используются для ее определения:
print( На сцене ставили \"Колобок\"")
#	Вывод: На сиене ставили "Колобок"
Необработанные строки
Однако экранирование каждого специального символа вручную может показаться неудобным, поэтому Python предлагает альтернативный способ: необработанные строки (англ. - raw strings). В таких строках любая комбинация символов, включающих обратный слэш, интерпретируется буквально, а не как управляющая последовательность.
Создать необработанную строку можно с помощью префикса г перед открывающей кавычкой строки:
path = r"C:\Python projects\task.ру"
print(path)
#	Вывод: C:\Python projects\task.py
Использование необработанных строк значительно упрощает работу с длинными последовательностями специальных символов. Однако, хотя в необработанных строках обратные слэши считаются обычными символами, экранирование самих кавычек всё равно остаётся необходимым для того, чтобы предотвратить досрочное завершение строки.
Кодирование символов
Компьютер хранит и обрабатывает информацию в виде чисел 0 и 1, поэтому каждому символу соответствует определённая числовая последовательность. Например, заглавная английская буква А может выглядеть как 01000001.
Основы Python 83
Процесс преобразования символов в числовые коды и обратно называется их кодированием.
Если каждый символ представляет собой последовательность из 8 бит (чисел 0 и 1), то такая система позволит представить до 28 = 256 различных символов. Однако этого может быть недостаточно для некоторых языков, ведь только в китайском языке тысячи иероглифов.
Исторически существовало множество различных кодировок, каждая из которых поддерживала определенный набор символов (например, ASCII для английского языка или КОИ-8 для кириллицы). Но это вызывало трудности при передаче текста между разными языками и платформами, так как без нужной кодировки текст становился нечитаемым.
Современные системы используют универсальный стандарт для кодирования практически всех существующих символов, включая кириллицу и даже эмодзи - Юникод (англ. - Unicode). Каждому символу в Юникоде сопоставляется определенное шестнадцатеричное значение с префиксом и+. Например, значение U+1F60A соответствует эмодзи a U+0410 - заглавной русской букве А.
Python разрешает напрямую встраивать символы Юникода:
print ('‘\H041f \u0440\li0438\u0432\U04 35\U0442 1 )
# Вывод' Привет!
Кодировка VTF-8
Юникод решает проблему совместимости разных кодировок благодаря единому набору символов, однако для фактического хранения символов в памяти компьютера нужен метод их представления в двоичной форме. Самым распространённым способом кодирования символов Юникода является использование кодировки L'TF-8 (от англ. Unicode Transformation Format, 8-bit - формат преобразования Юникода, 8-бит).
UTF-8 обладает рядом преимуществ, включая совместимость с ASCII (первые 128 символов Юникода соответствуют ASCII), а также возможность кодирования любых символов Юникода с использованием от 1 до 4 байт.
Например, для кодирования латинских символов достаточно одного байта (или 8 бит) - английская буква A (U+0041) кодируется как 01000001, однако кириллические буквы требуют двух байт - русская буква А (11+0410) кодируется как 11010000 10010000.
Основы Python 84
Функции для работы с кодированием символов
Python предоставляет встроенные инструменты для взаимодействия с внутренними представлениями символов. Основные из них - функции ord() (от англ, ordinal - порядковый номер) и chr() (от англ, character-символ).
Функция	ord(x)
Описание	Возвращает номер символа х в Юникоде
Параметры	• х - строка, представляющая собой один символ
Возвращаемое значение	Целое число
Функция	chr(n)
Описание	Возвращает символ в Юникоде по номеру п
Параметры	• п - порядковый номер символа в Юникоде
Возвращаемое значение	Строка
Функция ord() возвращает числовой эквивалент заданного символа в Юникоде, в то время как функция chr() делает противоположное и получает символ по указанному числу.
Например, получим порядковые номера некоторых символов: print(ord( а' )) # Вывод: 97 print(jrd( 1')) # Вывод: 49
А теперь по полученным порядковым номерам символов вернём сами символы'
print(chr(97)) # Вывод1 а print(chr(49)) # Вывод: 1
Основы Python 85
Примеры
Пример I. Уведомление о подтверждении доставки в интернет-магазине
Уведомление о подтверждении доставки, отправляемое на электронную почту, содержит отформатированную строку с адресом:
#	Данные пользователя
user_name = “Вавилов Николай Иванович ' address = "ул. Ленина, д. 15, кв. 35” city = "Москва"
#	Форматируем адрес с помощью \п и \t delivery address = (
f ’Доставка для: {user_name}\n f"\tAflpec: { addressJAn f’Atropofl: (city}\n"
)
print(delivery_address)
Вывод:
Доставка для: Вавилов Николай Иванович Адрес: ул. Ленина, д. 15, кв. 35 Город: Москва
Пример 2. Путь к файлу с логами
Серверное приложение записывает логи в файл:
#	Путь к файлу с логами
escapedpath = "С:\\serverWlogsWapp.log" print(f Логи хранятся пс пути: {escaped_path}")
Вывод:
Логи хранятся по пути: С:\server\logs\app.log
Пример 3. Смс-уведомление с эмодзи о готовности заказа
Интернет-магазин отравляет уведомление с эмодзи о готовности заказа:
#	Текст SMS с эмодзи (Юникод)
sms_text = Ваш заказ №42 готов к выдаче! \U0001F69A
print(sms_text)
Вывод:
Ваш заказ №42 готов к выдаче!
Основы Python 86
Итоги
J Управляющие символы - это последовательности символов, которые начинаются с обратного слэша (\) и используются для специальных действий в строке: \п для переноса строки, \t для добавления табуляции, \г для возврата каретки и \Ь для удаления последнего символа.
J Экранирование символов с помощью дополнительного обратною слэша (\) используется для того, чтобы специальные символы воспринимались как часть строки.
J Необработанные строки создаются добавлением префикса г перед строкой. В таких строках символ \ не обрабатывается как начало управляющего символа.
Юникод - это универсальный стандарт для кодирования большинства существующих символов,
V UTF-8 это самая распространённая кодировка для хранения символов Юникода Она использует от 1 до 4 байт для кодирования символов
J Функция ord() возвращает Юникод-номер заданного символа.
J Функция chr() возвращает символ по его Юникод-номеру.
Задания для самопроверки
1.	Что делает управляющий символ \п?
2.	Используйте управляющие символы и выведите на экран следующую строку:
Величина	Единица
Сила света	кандела
Сила тока	ампер
3.	Что такое Юникод?
4.	Используйте экранирование символов и выведите на экран следующие строки:
C:\Program files\browser\test A:\necessary files\timetable D:\reading\bulgakov
5.	Чем отличаются функции ord() и chr()?
Основы Python 87
2.	8 ВВОД И ВЫВОД ДАННЫХ
Важной частью любой программы являются операции ввода-вывода: получение данных от пользователя и отправка результатов обратно. В самом простом виде в Python это реализуются в виде двух встроенных функций: уже знакомой нам функции print () для вывода данных на экран, и функции input () для получения данных от пользователя.
Вывод данных
Функция print () - одна из наиболее используемых функций в Python, позволяющая выводить различные объекты на экран.
Функция	prlnt(*objects , sep=" ", end="\n")
Описание	Выводит все элементы objects, разделяя их строкой sep, и завершая вывод строкой end • *objects - последовательность объектов, которые необходимо вывести на экран
Параметры	Необязательные параметры: •	sep - строка-разделитель объектов ’objects, по умолчанию sep=" " •	end - строка, которой завершается вывод всех объектов ’objects, по умолчанию end="\n"
Возвращаемое значение	None
Символ * в параметре ’objects означает, что функции print() может быть передано любое количество объектов, перечисленных через запятую:
print(l, 2, 3)
#	Вывод: 12 3
print ('3 в квадрате равно", 3 ” 2)
#	Вывод: 3 в квадрате равно 9 message = "Купить яйца: ' print(message, 10)
#	Вывод: Купить яйца 10
По умолчанию все объекты разделяются пробелом, а в конце добавляется символ переноса строки (\п), поэтому каждый следующий вызов функции Основы Python 88
print() выводит данные на новой строке. Однако такое поведение можно изменить с помощью параметров sep (от англ, separator - разделитель) и end (с англ. - конец).
При изменении параметра sep все значения разделяются между собой его значением, а не пробелом. В качестве разделителя можно передать любую строку:
print(1, 2, 3, sep= | ")
#	Вывод: 3/2/3
print( Ручка , "карандаш , "Линейка", sep="_“)
#	Вывод: Ручка_Карандай1_Линейка print( х", "у", "z", sep=’\t )
#	Вывод: х у z
Параметр end изменяет завершение строки, например, чтобы следующий вывод шел сразу за предыдущим:
print( Сегодня идёт дождь', end=", )
print('HyxHO взять зонт")
Тогда в результате на экран будет выведена одна строка, а не две:
Сегодня идёт дождь, нужно ззять зонт
Ввод данных
Получение данных от пользователя позволяет создавать по-настоящему интерактивные программы и осуществляется с помощью функции input(). Когда программа доходит до вызова input() она приостанавливает свое выполнение и ждет, пока пользователь введет текст и нажмет клавишу Enter.
Функция
Описание
Параметры
Возвращаемое значение
input(help)
Возвращает строку, введённую пользователем
Необязательные параметры:
• help - строка-подсказка, которая может отображаться перед ожиданием ввода
Строка
Функция input () может принимать одно необязательное строковое сообщение, которое отображается перед вводом, и возвращает строку, введённую
Основы Python 89
пользователем. Например, давайте попросим пользователя ввести его имя. а затем выведем приветствие:
name = input("Пожалуйста, введите своё имя: ") print(f"Здравствуй, дорогой друг {name}! ) # Ввод: Пожалуйста, введите своё имя: Бильво # Вывод: Здравствуй, дорогой друг Бильбо!
Независимо от введённых данных, функция input() всегда возвращает строку. Чтобы продолжить работу с введёнными данными, как с числами, их нужно привести их к нужному типу с помощью функций преобразования, таких как int() или float():
nl = int(input("Введите первое число: ))
n2 = int(input("Введите второе число: "))
print(f'CyMMa чисел <nl> и {п2} равна {nl + п2}")
#	Ввод: Введите первое число- 2
#	Ввод: Введите второе число: 3
#	Вывод: Сумме- чисел 2 и 3 равна 5
Функции input() и print() часто используются вместе для создания интерактивных программ, которые запрашивают у пользователя данные, которые обрабатывают и отображают результаты.
Примеры
Пример I. Форма регистрации на сайте
Пользователь регистрируется на сайте и вводит данные для создания аккаунта: логин, почту и пароль:
#	Форма регистрации нового пользователя
login = input( Придумайте логин: )
password = input( 'Придумайте пароль: )
email = input( '.ведите вашу электронную почту: ")
#	Вывод приветственного сообщения
print(f"=================================\пСпасибо за регистрацию, {login}!")
print(f"На вашу почту {email} отправлено письмо с подтверждением.")
Вывод:
Придумайте логин: dragon99
Придумайте пароль: qwertyl23
введите вашу электронную почту: red_drago990mail.ru
Спасибо за регистрацию, dragon99!
На вашу почту red_drago990mail.ru отправлено письмо с подтверждением.
Основы Python 90
Пример 2. Калькулятор чаевых
Программа рассчитывает сумму чаевых на основе счета и процента, который вводит пользователь:
#	Ввод пользователем суммы счёта и процента чаевых
bill = flo<at(input( 'Введите сумму счета: "))
tip_percent = float(input( 'ведите процент чаевых (например, 10):	))
#	Расчёт чаевых
tip_amount = bill * (tip_percent / 100)
#	Подсчёт итоговой суммы total = bill + tipamount
#	Вывод рассчитанных сумм на экран
print( ХпИтоговая сумма:", f"{total:.2f} руб. , sep='\t\t')
print( Из них чаевые: , f'{tip_amount:.2fJ руб.", sep=\t\t")
Вывод:
введите сумму счета: 2250
Введите процент чаевых (например, 10): 12
Итоговая сумма:	2520.00 руб.
Из них чаевые:	270.00 руб.
Итоги
S Функция print() предназначена для вывода данных на экран.
По умолчанию объекты, выводимые на экран, разделяются пробелом, а строка завершается переносом строки. Это можно изменить с помощью параметров sep и end
J Функция input () предназначена для получения данных от пользователя и всегда возвращает строку.
J Перед ожиданием ввода может быть выведена строка-подсказка, переданная функции input() в качестве параметра.
Задания для самопроверки
1.	Что будет выведено на экран в результате выполнения данного кода? print(’\"Хлеб", "молоко", "колбаса", sep=", ", end= ) print(’ещё", "наверное", "конфеты , sep«", ", end="!\" - ) print(’думал", "Иван", end=". )
2.	В следующих переменных хранятся личные данные пользователя:
name = "Пьер"
Основы Python 91
last_name = "Безухов"
Вставьте значения этих переменных в строку так, чтобы в результате на экран было выведено:
Добрый вечер, дорогой Пьер Безухов!
3.	Запросите у пользователя два целых числа и выведите на экран их сумму в виде строки "Число1 + Число2 = Результат".
Пример входных данных Пример выходных данных 1	1 + 2 = 3
2 10	10 + 12 = 22
12 34	34 + 6 = 40
6
4.	Какой тип данных возвращает функция input()?
5.	Запросите у пользователя название фильма и выведите его на экран в виде строки "Мне тоже нравится фильм {название}".
Пример входных данных Пример выходных данных
Звёздная пыль	Мне	тоже	нравится	фильм	"Звездная	пыль"!
Терминатор 2	Мне	тоже	нравится	фильм	"Терминатор	2"!
Тор	Мне	тоже	нравится	фильм	"Тор"!
2.9 ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ И ОПЕРАТОРЫ
Логическими выражениями называют утверждения, которые могутбыть оценены как истинные или ложные. Например, выражение «Идёт дождь» в зависимости от погоды будет истинным или ложным. Так и в Python результатом вычисления любого логического выражения всегда будет значение ло« ического типа данных True или False.
Логические выражения в сочетании с ветвлением являются фундаментальным инструментом в программировании, позволяя выполнять различные действия в зависимости от того, выполняются ли определенные условия. С их помощью можно реализовать проверку пароля, фильтрацию данных или управление потоком выполнения программы.
Основы Python 92
Логический тип данных также называют булевым типом (англ. - boolean) в честь британского математика и философа Джорджа Буля (англ. - George Boole). Именно он в середине 19 века предложил систему рас-суждений, где каждое высказывается либо истинное, либо ложное, что легло в основу булевой алгебры Благодаря работе Буля современные компьютеры обрабатывают данные, основываясь на простейших двоичных состояниях: 1 и 0, соответствующих лотиче-
ским понятиям истинности и ложност и.
Логические значения объектов
Любой объект в Python обладает определенным логическим значением, которое можно получить с помощью встроенной функции bool() (от англ, boolean - булев (логический) тип данных).
Функция	bool(obj)
Описание	Возвращает логическое значение объекта obj
Параметры	• obj - объект, логическое значение которого необходимо определить
Возвращаемое значение	True пли False
Функция ьоо1() возвращает True, если объект считается истинным, и False, если объект считается ложным.
К истинным значениям относятся любые непустые объекты и ненулевые числа:
print(bool( В саду распустились розы")) # Вывод: True
print(bool({"Имя": "Копатыч", "Место жительства": "Долина Смешариков"})) # Вывод: True
print(bool(256))
# Вывод: True
Основы Python 93
К ложным значениям относятся любые пустые объекты, число 0 любою типа и объект None: print(bool( )) # Вывод: FaLse print(bool(0.0)) # Вывод: FaLse print(bool(Nene)) if Вывод: FaLse
Связь с типом целых чисел
Булевы значения True и False тесно связаны с целочисленным типом данных (int): True соответствует числу 1, a False - числу е. Мы даже можем явно преобразовать булевы значения в целые числа с помощью функции int(): print(int(True)) # Вывод: 2 print(int(False)) # Вывод: 0
Данная особенность может применяется в арифметических операциях, хотя это не является распространенной практикой.
Операторы сравнения
Для проверки отношения между объектами используются операторы сравнения. Их цель - сравнение содержимого объектов, чтобы определить отношение между двумя величинами. Результатом сравнения всегда является булево значение, указывающее на истинность или ложность выражения.
Таблица 7 - Операторы сравнения
Опе-	На танке	Описание	Пример	Ре-
ра юр				зультат
>	Больше	Если левый операнд больше правого, возвращает True, иначе -False	4 > 76 23 > 54	True False
>=	Больше или	Если левый операнд больше или	14 >= 14	True
	равно	равен правому, возвращает True, иначе - False	22 >= 39	False
Основы Python 94
Продолжение таблицы 7 - Операторы сравнения
Оператор	Название	Описание	Пример	Ре- зуль тат
<	Меньше	Если левый операнд меньше правого, возвращает True, иначе -False	12 < 93 56 < 3	True False
<=	Меньше или	Если левый операнд меньше или	45 <= 45	1 rue
	равно	равен правому, возвращает True, иначе - False	34 <= 19	False
	Равно	Если левый операнд равен правому, возвращает True, иначе -False	73 == 73 34 == 12	True False
! =	Не равно	Если левый операнд не равен правому. возвращает True, иначе -	12 ! = 32 37 != 37	True False
False
Сравнение чисел в Python работает точно так же, как и в обычной матема
тике:
print(21 < 42) # Вывод: True print(12 <= 1) # Вывод: FaLse print(14 == 17) # Вывод: FaLse
Важно различать оператор сравнения на равенство == и оператор присваивания =. Они выполняют совершенно разные задачи, и неправильное их использование приведёт к ошибкам.
Сравнение строк
Операторы сравнения могут применяться и к строкам. В этом случае
Основы Python 95
строки сравниваются лексиктирафически (посимвольно) на основе их порядковых номеров в Юникоде. Сначала сравнивается первый символ каждой строки, затем второй и так далее. Строчные буквы считаются меньше прописных, а латинские символы больше кириллических.
Например, если сравнить строки, содержащие строчные английские буквы "а" и "Ь", то строка с буквой "а" будет меньше:
print("a" < "b") # вывод: True
Это связано с тем, что порядковый номер символа "а" в Юникоде меньше, чем порядковый номер символа "Ь". Убедимся в этом, получив номера символов с помощью функции ord(): print(ord( а )) # Вывод: 97 print(ord("b’ )) # Вывод: 98
Так как 97 < 98. то и "а" < "Ь".
Также при сравнении учитывается регистр букв: заглавная буква считается меньшей, чем строчная:
print( Груша" > "груша") # Вывод: False
print( большой театр" >= ’Большой театр") # Вывод: True
Если символы совпадают, то больше будет так строка, у которой больше длина:
print( Гил" < "Голова')
#	Вывод: True
print( Котёнок" > "Кот”) # Вывод: True
Поэтому строки равны только в том случае, если у них совпадают все символы и длина строки.
Опера 1 оры идет ичиости
В отличие от оператора сравнения на равенство (==), который проверяет равенство значений, операторы идентичности is и is not проверяют указывают ли две переменные на один и тот же объект в памяти компьютера.
Основы Python 96
Таблица 8 - Операторы идентичности
Опера-	Описание	Пример	Ретуль-
тор			тат
is	Если оба операнда ссылаются на один и тот	х is у	True или
	же объект в памяти, возвращает True, иначе		False
	- False		
is not	Если оба операнда ссылаются на разные объ-	х is not у	True или
	екты в памяти, возвращает True, иначе False		False
На практике операторы is и is not чаще всего применяются при сравнении с None для проверки на отсутствие значения, так как None представляет собой уникальный объект, и любое другое значение гарантированно не совпадает с ним по ссылке:
apples = None
print(apples is None)
#	Вывод: True
melons = 100
print(melons is None)
#	Вывод: FaLse
Операторы идентичности следует использовать с осторожностью при сравнении изменяемых объектов (таких как списки и словари), так как они проверяют равенство идентификаторов объектов, а не их содержимого.
Например, создадим две переменные целочисленного типа данных (неизменяемого) с одинаковым значением и увидим, что обе будут ссылаться на один объект в памяти компьютера: numberl = 10 number? = 10 print(numberl is number?) # Вывод: True
Однако для каждого из списков (изменяемого типа данных), несмотря на одинаковое содержимое, будут созданы новые объекты:
fruitsl = ("Яблоки", "Груши J
fruits? = ["Яблоки , "Груши ] print(fruitsl is fruits?) # Вывод: FaLse
Но если одной переменной присваивается значение другой, они будут
Основы Python 97
ссылаться на один и тот же объект:
fruitsl = ["Яблоки", "Груши"]
fruits2 = fruits_l
print(fruitsl is fruits_2)
#	Вывод: True
Логические операторы
Лот ические операторы выполняют лог ические операции над простыми выражениями, объединяя их в единое сложное выражение. Основываясь на законах алгебры логики, в Python выделяют три основные логические операции: отрицание. конъюнкцию и дизъюнкцию.
Отрицание изменяет значение условия па противоположное, фактически являясь инверсией. В реальной жизни аналогичную роль играет частица «не». Предположим, вы заявляете друзьям в кафе: «Мы закажем пиццу с ананасами», но ваш приятель отрицает ваше высказывание: «Мы не закажем пиццу с ананасами».
Интуитивно понять смысл терминов «конъюнкция» и «дизъюнкция» может быть сложно, так как они заимствованы из латыни. Конъюнкция буквально означает «связывание», отражая требование выполнения всех заданных условий одновременно, поэтому её ассоциируют с союзом «и». Так, если кто-то заявит: «Мы закажем и пиццу с ананасами, и пиццу с грибами», то его высказывание будет истинным только, если будут заказаны обе пиццы.
Что касается дизъюнкции, то её корень восходит к слову «разделять» и сама операция приближена к союзу «или». Она воплощает понятие альтернативы, подразумевая выбор одного или обоих из предлагаемых вариантов. Подобно ситуации, когда кто-то скажет: «Мы закажем или пиццу с трибами, или пиццу с курицей», и он будет прав как в случае заказа одной из перечисленных пицц, так и сразу обеих.
В традиционной булевой алгебре логические операции аналогичны арифметическим действиям, поэтому конъюнкцию также называют логическим умножением, а дизъюнкцию - логическим сложением.
В Python рассмотренные логические операции реализуются тремя основными операторами: not (с англ. - не), and (с англ. - и) и or (с англ. - или). Первый реализует отрицание, второй - конъюнкцию, а третий - дизъюнкцию.
Основы Python 98
Таблица 9 - Логические операторы
Оператор	Операция	Описание	Пример	Ре- зультат
not	Отрицание	Изменяет значение операнда на противоположное: True становится False, а False — True.	not True	False
and	Конъ- юнкция	Возвращает True, если оба операнда истинны, иначе - False.	True and False	False
or	Дизъ- юнкция	Возвращает True, если хшя бы один из операндов истинен, иначе - False.	True or False	T rue
Логические операторы работают с логическими значениями (True и False), а их приоритет аналогичен тому, который установлен для арифметических операций:
I.	Отрицание.
2.	Конъюнкция (логическое умножение).
3.	Дизъюнкция (логическое сложение).
Однако с помощью круглых скобок можно изменять приоритет выполнения операторов, например, чтобы сначала выполнился оператор or, и только затем and:
print(i8 > 16 and (4 == 5 or 1 > 0)) # Вывод: True
Ло1 ическое отрицание
Задача оператора not состоит в изменении исходного значения на противоположное. Так истинное значение становится ложным и наоборот: print(not(True)) # Вывод: False print(not(10 > 0)) # Вывод: False
Конъюнкция: логическое умножение
Результат конъюнкции или логического умножения с помощью оператора and будет истинным лишь тогда, когда каждое объединяемое выражение истинно. Поэтому его также называют ло1 ическим «И»:
print(20 >= 18 and 20 <= 21)
Основы Python 99
# Вывод: True
Процесс вычисления такого выражения происходит поэтапно:
I Вычисляется значение первого операнда: 20 >= 18 (Результат-True).
2.	Вычисляется значение второго операнда: 20 <= 21 (Результат - True).
3.	Вычисляется значение полученного выражения: True and True (Результат - True).
Однако если в первом выражении был получен False, то все следующие шаги будут пропущены. Это называется ленивым вычислением, так как возвращается первый полученный False:
print(12 >= 38 and 12 <= 21) # Вывод: FaLse
Здесь значение выражения 12 <= 21 даже не вычислялось, так выражение 12 >= 18 является ложным.
Дизъюнкция: логическое сложение
В отличии от конъюнкции результат дизъюнкции будет истинным, если истинно хотя бы одно из объединяемых выражений, поэтому её также называют логическим «ИЛИ»:
print(17 >= 18 or 27 <= 35)
#	Вывод: True
Такое выражение тоже вычисляется последовательно, пока не встретится первое истинное значение. После этого выражение возвращает True и дальнейшие вычисления не происходят.
Но если при вычислении значений всех выражений не было получено ни одного значения True, то будет возвращен False.
print(17 >= 18 or 17 <= 12)
#	Вывод: FaLse
Примеры
Пример 1. Доступ к фильмам 16+ в онлайн-кинотеатре
В оплайн-кипотеатре для доступа к фильмам с возрастным рейтингом 16+ пользователь должен соответствовать определённым требованиям: он должен быть зарегистрирован и быть старше 16 лет:
#	Данные пользователя
user_age = 20
Основы Python 100
is_registered = True
#	Определяем наличие доступа к фильму has_access = user_age >= 16 and is_registered print(f"Имеет ли пользователь доступ к фильму? {hasaccess}')
Вывод:
Имеет ли пользователь доступ к фильму? True
Пример 2. Оценка состояния товара на складе
Программа складского учёта определяет состояние товара на складе, которое требует принятия мер Такой товар может находится в недостатке (меньше 10), в избытке (больше 100). или же его вовсе нет в наличии:
#	Количество товара на складе
product_stock = 5
#	Проверяем, находится ли товар в недостатке is_low_stock = product_stock < 10
print(f"Товара на складе: {product_stock} шт. Дефицит? {is_low_stock}")
#	Проверяем, находится ли товар в избытке
is__overstockea = product_stock > 10и print(f"Избыток товара? {isoverstocked}')
#	Проверяем, отсутствует ли товар на складе
is_out_of_stock = product_stock == 0
print(f''Тс--ара нет в наличии? {is_out_of_stock}")
#	Проверяем необходимость принятия мер requires_attention = is_low_stock or is_overstocked or is_out_of_stock print(f"Требует ли товар нчимания (дефицит/избыток/отсутствие)?
{requires_attention}")
Вывод:
Товара на складе: 5 шт. Дефицит? True
Избыток товара? False
Товара нет в наличии? False
Требует ли товар внимания (дефицит/избыток/отсутствие)? True
Пример 3. Проверка сложности задачи
Система оценивает сложность задачи на основе времени выполнения и задействованных ресурсов. Задача считается сложной, если на её выполнение требуется больше 10 часов или больше 5 единиц ресурсов. При этом задача не будет считаться сложной, если при любом затрачиваемом времени требуется всего 1 единица ресурсов:
Основы Python 101
#	Ввод пользовательских данных
time_required = int(input( Оценочное время выполнения (часы); ))
resources_needed = int(input('Количество задейстр’ранных ресурсов (ед.): "))
#	Определение сложности задачи
is_complex = (time_rpquired > It) or resourcesjieeded > 5) and not re-sources_needed == 1
print ("Задача сложная?", is_cornplex)
Вывод:
Оценочное время выполнения (часы): 15 Количество задействованных ресурсов (ед.): 1 Задача сложная? False
Итоги
J Логические выражения - это утверждения, которые могут быть оценены как истинные (True) или ложные (False).
J True и False-это единственные значения логического (б) лева) типа данных.
✓ Функция bool() возвращает логическое значение объекта. К истинным значениям относятся непустые объекты и ненулевые числа, а к ложным -пустые объекты, число 0 любого типа и объект None
Операторы сравнения (>, >=, <, <=, !=, ==) определяют отношение между двумя величинами. Числа сравниваются арифметически, а строки - лек-сикот рафически
Операторы идентичности (is и is not) проверяют ссылаются ли две переменные на один и тот же объект в памяти компьютера.
J Логические операторы выполняют логические операции над простыми выражениями, объединяя их в единое сложное выражение.
Лот ическое отрицание (оператор not) изменяет логическое значение на про тивоположное.
Логическое умножение или конъюнкция (оператор and) возвращает True, если оба объединяемых выражений истинны.
J Логическое сложение или дизъюнкция (оператор or) возвращает True, если хотя бы одно объединяемое выражение истинно.
Задания для самопроверки
1.	Какие из следующих объектов являются истинными?
Основы Python 102
n = 20 reply = message = "Сегодня выпало много снега" isadmin = False count = 0
2.	Что будет выведено на экран в результате выполнения данного кода?
print(i45 < -1)
print(89 != 19)
print( Сегодня солнечно" == "Сегсдня солнечно )
print(91 >= -1)
print("Доступ разрешён" 1 = "Доступ запрещён")
3.	В каком порядке выполняются логические операции при отсутствии скобок?
4.	Что будет выведено на экран в результате выполнения данного кода?
print(92 > 1 or "Кто ты" 1= "Кто я")
print(not(84 == 84))
print(23 > 1 and not(90) or 1 < -9)
print(10 != 23 or 1 > 2 and "Яблоко == "Яблоко")
print(98 <= 100 and 12 != 98 and 78 > -1 and not(l > 2))
5.	Запросите у пользователя год рождения и выведите на экран True, если введённое значение лежит в пределах от 2000 до 2004 включительно, иначе False.
Пример входных данных Пример выходных данных
2004	True
2000	True
1990	False
2.10 ВЕТВЛЕНИЕ
Ветвление - это фундаментальный механизм в протраммировании, позволяющий выполнять различные действия в зависимости от условия. Это похоже на принятие решений в реальной жизни: «Если я наберу 100 очков, то перейду на следующий уровень. Иначе мне придётся сыграть ещё раз».
Условные операторы
В Python ветвление реализуется с помощью условных операторов if (с англ. - если), el if (от англ, else if - иначе если) и else (с англ. - иначе):
if условие_1:
Основы Python 103
6лок_кода_1
elif условие_2:
блг>к_к ода._2
else:
блок_кода_3
Каждое условие - это логическое выражение, результат которого может быть либо истинным (True), либо ложным (False). Если первое условие в if истинно, то выполняется соответствующий блок кода, иначе - проверяется следующее условие в elif и так далее. Если все условия оказались ложными, то выполняется блок кода else.
Принадлежность кода к блоку if, elif или else определяется обязательным отступом (обычно 4 пробела). Отсутствие или неправильный отступ приведет к ошибке IndentationError.
Оператор if
Самая простая форма ветвления - это просто оператор if. В этом случае определенный блок кода выполняется только при истинности заданного условия.
points = int(input("BBeflMTe количеств набранных очков; )) if points < 100:
prinv( Вам пока не хватает очков для завершения игры! :( )
#	Ввод: Введите количество набранных очков: 5
#	Вывод: Вам пока не хватает очков для завершения игры! :(
Если введённое пользователем число меньше 100, программа выведет сообщение о неудаче. В противном случае ничего не произойдет.
Операторы elif
Оператор elif добавляет альтернативные сценария выполнения программы. При этом количество блоков elif не ограничено, но условие в каждом них проверяется только в том случае, если условие в if или в предыдущем elif было ложным;
points = int(input('Введите количество набранных очков )) if points < 100:
print( Вам пока не хватает ичков для завершения игры! :( )
elif points >= 1и0:
print( Поздравляем! Вы набрали нужное количество очков! :)")
#	Ввод: Введите количество набранных очков: J 00
#	Вывод: Поздравляем! Вы набрали нужное количество очков! :)
Теперь, если условие points < 106 окажется ложным, протрамма перейдёт ко второму условию points >= 100. Если оно будет истинным, пользователь Основы Python 104
увидит сообщение об успехе.
Сложные условия с логическими операторами
Для создания сложных условий используются логические операторы and. or и not. Они объединяют несколько простых условий в более сложные, например. для проверки принадлежности значения к определенному диапазону:
points = int(input( введите количество набранных очков )) if points < 300:
print( Вам пока не хватает очков для завершения игры! :(")
elif points >= 100 and points <= 110:
print( Поздравляем! Вы отлично сыграли! Ваш ранг: А :)")
elif points >= 111 and points <= 150:
print( Поздравляем! Вы великолепно сыграли! Ваш ранг: S :)")
#	Ввод: Введите количество навранных очков: 120
#	Вывод: Поздравляем' Вы великолепно сыграли! Ваш ранг: S :)
Математически оба сложных условия можно записать как 100 < points < 110 и 111 < points < 150. Например, выражение points >= 100 and points <= 110 будет истинным, если введённое пользователем число одновременно меньше или равно 110 и больше или равно 100.
Оператор else
Блок кода, следующий за оператором else, является необязательной частью условной конструкции if/elif/else Он выполняется в том случае, если не было выполнено ни одно из предшествующих условий:
points = int(input("Введите количество набранных очков: "))
if points < 300:
print( Вам пока не хватает очков для завершения игры! :( )
elif points >= 100 and points <= 110:
print( Поздравляем! Бы -.тличнс сыграли! Ваш ранг: А :)")
elif points >= 111 and points <= 150:
print( Поздравляем! Вы великолепно сыграли! Ваш ранг: S :)")
else:
print( Слишком большое количество очков! Может быть вы жульничали? D:")
#	Ввод' Введите количество набранных очков: 100500
#	Вывод: Слишком большое количество очков! Может быть вы жульничали? D:
Теперь, если введено недопустимое значение (больше 150), программа сообщит об ошибке.
Основы Python 105
Вложенное ветвление
В некоторых ситуациях возникает необходимость проверить дополнительные условия внутри уже существующего условия. Такое вложение условий называется вложенным ветвлением
Представим ситуацию, когда необходимо проверить два условия последовательно: сначала убедиться, что трок набрал достаточное количество очков, а потом проверить, прошел ли он проверку на робота:
points = int(input("Введите количество набранных очков )) if points >= 100:
print( Отлично! Вы набрали достаточное количество очков!') isbottestpassed = input( Вы прошли проверку на робота? (Да/Нет): ) if is_bot_test_passed == 'Да':
print( Поздравляем! Вы успешно завершили игру! :)") else:
printC К сожалению, проверка на робота не пройдена. Попробуйте еще раз.") else:
print( Вам пока не хватает очков для завершения игры! :(") # Ввод: 120 # Вывод: Отлично! Вы набрали достаточное количество очков! # Вывод: Вы прошли проверку на робота? (Да/Нет): # Ввод: Да # Вывод: Поздравляем! Вы успешно завершили игру! :)
Как видно из примера, второе условие is_bot_test_passed == "Да" проверяется только после того, как первое условие points >= 100 выполнено. Это помотает структурировать код таким образом, чтобы каждый шаг зависел от предыдущего результата.
Однако чрезмерное вложение делает код сложнее для чтения и понимания. Поэтому рекомендуется избегать большою числа уровней вложенности. Даже в философии Python сказано: «Плоское лучше, чем вложенное», поэтому следует избегать глубоких вложений, особенно если задачу можно решить другим способом.
Тернарный оператор
Для простого условного выбора в одну строку Python предлат ает компактную запись с помощью тернарного онера гора:
значение! if условие else значение2
Этот оператор возвращает значение!., если условие истинно, и значение2.
Основы Python 106
если условие ложно.
Тернарный оператор часто используется для присваивания значения переменной в зависимости от условия:
points = int(input("Введите количество очков ))
result = Вы проиграли :(" if points < 100 else 'Вы выиграли :)" print(result) # Ввод: Введите количество очков 11в
#	Вывод: Вы выиграли :)
Этот код эквивалентен обычной конструкции if/else:
points = int(input('Введите количество очков: )) if points < 100:
result = "Вы выиграли :)"
else:
result = "Вы проиграли :("
print(result)
#	ввод: Введите количество очков: 25
it Вывод: Вы проиграли :(
Сопоставление с шаблонами
Начиная с Python 3.10, в языке появилась мощная конструкция сопоставления с шаблонами (англ. - match case), которая предоставляет более структурированный и читаемый способ обработки различных значений переменной по сравнению с длинными цепочками if/elif/else;
match выражение: case шаблон_1.
дейстбия_при_совпадении_с_шаблйном_1
case шаблон_2:
дейс гвия_при_совпадении_с_шаблоном_2
case _:
дейстйия_если_ни_один_ша6лон_не_подошёл
Сопоставление с шаблонами позволяет выполнить действия в зависимости от того, какому шаблону соответствует переменная. Например, напишем программу, которая в зависимости от дня недели говорит что-нибудь хорошее:
day = input("Введите день недели ) match day:
case "Понедельник":
print( Понедельник - день кггда, все будет хорошо!") case "вторник":
print( Вторник - самое время начать что-нибудь новенькое!")
case "Среда":
рг!пТ("Середина недели - только вперед к новым вершинам!")
Основы Python 107
case '‘Четверг":
print( Четверг - отличный повод задуматься о том, сколько всего хорошего впереди! )
case "Пятница":
print(‘Пятница - конец рабочей недели! Время расслабиться!")
case ‘Суббита" | воскресенье":
print( Выходные - отличная возможность зарядиться энергией для но-в^-й недели!")
case _:
print( Кажется, что-то пошло не так...")
#	Ввод: Введите день недели: Среда
#	Вывод: Середина недели - только вперед к новым вершинам!
#	Ввод: Введите день недели: День недели
#	Вывод: Кажется, что-то пошло не так...
Конструкция match/case принимает выражение (в данном случае значение переменной day) и сравнивает его последовательно с каждым case. Если значение совпадает с шаблоном в case, выполняется соответствующий блок кода.
Оператор I внутри case выполняет функцию оператора ог и объединяет несколько шаблонов (в данном случае "Суббота" и "Воскресенье").
Подчеркивание (_) вместо шаблона в последнем случае используется для значения по умолчанию. Оно соответствует любому значению, которое не совпало ни с одним из предыдущих шаблонов и похоже на оператор else в конструкции if/elif/else.
Сопоставление с шаблонами также поддерживает сопоставление не только с конкретными значениями, но и с типами данных, последовательностями и другими объектами, что делает его очень полезным для обработки сложных сценариев ветвления.
Примеры
Пример 1. Сист ема скидок в интернет-магазине
Интернет-магазин предоставляет скидку в зависимости от итоговой суммы заказа:
order_amount = fioat(input( Введите сумму заказа: ))
#	Определение скидки (%)
if nrder_amount > 10000: discount = 2£>
elif order_amount > 5000:
discount = 15
elif orderamount > 2500:
Основы Python 108
discount = 10 else:
discount = 0
#	Расчет суммы с учетом скидки
total = order_amount * (1 - discount / 10t>) print(f"Ваша скидка: {discount}%")
print(f Итог .взя сумма: {total:.2f} руб.')
Вывод:
Введите сумму заказа: 6500
Ваша скидка: 15%
Итоговая сумма: 5525.00 руб.
Пример 2. ()i раничение доступа к сайту ио возрасту
Доступ к сайту запрещён для лиц младше 18 лет:
age = int(input( ведите ваш возраст: ))
access = "Доступ разрешен" if age >= 18 else "Доступ запрещен" print(access)
Вывод:
введите ваш возраст: 21
Доступ разрешён
Пример 3. Обработка заказа в интернет-магазине
Система определяет действие по заказу в зависимости от ею статуса:
crder_status = input("Введите статус заказа: )
match order_status:
case "Новый":
action = "Назначить менеджера"
case В обработке":
action = "Проверить наличие товара"
case "Доставлен":
action = "Отправить опрос клиенту'
case ’Отменён":
action = "Уточнить причину отмены"
case _:
action = "Статус не распознан"
print(f Действие: {action}")
Вывод:
Введите статус заказа (Новый/i4 обработке/Доставлен/Отменен): Доставлен Действие: Отправить опрос клиенту
Основы Python 109
Итоги
s Вет вление (if/elif/else) - это механизм, позволяющий выполнять разные блоки кода в зависимости от истинности или ложности определенных условий. Если первое условие в if истинно, то выполняется соответствующий блок кода, иначе - проверяется следующее условие в elif и так далее. Если ни одной из условий не выполняется, то выполняется блок кода else.
J Сложные условия создаются объединением простых условий с помощью логических операторов and, or, not.
J Тернарный оператор (значение! if условие else значение?) - это компактная запись для простого условного выбора в одну строку. Он возвращает значение!, если условие истинно, иначе - значение?.
Сопоставление с шаблонами (match/case) - это механизм, позволяющий обрабатывать различные значения переменной при множестве условий. Если значение переменной в match совпадает с шаблоном в case, выполняется соответствующий блок кода.
J Оператор | объединяет несколько шаблонов в одном case
J Символ _ используется для определения шаблона по умолчанию.
Задания для самопроверки
I.	Запросите у пользователя целое число от 1 до 7, обозначающее номер дня недели, и выведите на экран название этого дня.
Если введённое число выходит за пределы диапазона целых чисел от 1 до 7, то выведите строку "Такого дня недели не существует".
Пример входных данных Пример выходных данных
1	Понедельник
5	Пятница
87	Такого дня недели	не	существует
2.	Запросите у пользователя два целых числа и один из основных арифметических операторов (+, -, * и /). Выведите на экран результат арифметической операции между введёнными числами в зависимости от введённого оператора.
Если вводится арифметический оператор не из списка, то выведите строку "Неизвестный оператор".
Основы Python 110
Пример входных данных Пример выходных данных
1	3
2
+
30	29
1
45	Неизвестный оператор
2
О/
/о
3.	Запросите у пользователя два целых числа, и если они оба положительные, то выведите на экран наибольшее из них, а если оба отрицательные -
наименьшее.
Если знаки чисел не совпадают или одно или оба равны нулю, то выведите оба числа через запятую.
Пример входных данных Пример выходных данных
18	18
2
-50	-50
-12
-10	-10, 20
0
4. Запросите у пользователя два целых числа, и если они являются решением уравнения 61 + х = 98 — у, то выведите на экран строку "Да", иначе - "Нет". Пример входных данных Пример выходных данных
12 98 19 18 1 15	Нет Да Нет
5. Запросите у пользователя целое грёхзначное число, и если его первая и последняя цифра совпадают, то выведите на экран строку "Да", иначе - "Нет".
Подсказка', вспомните про операторы целочисленного деления (//) и нахождения остатка от деления (%).
Основы Python 111
Пример входных данных
143
767
292
Пример выходных данных
Нет
Да
Да
2.11 ЦИКЛЫ
В процессе разработки программ часто возникает необходимость многократно выполнять одни те же действия. Например, представим, что мы управляем роботом на клетчатом поле, и за одно действие можем переместить его только на одну клетку. Чтобы сдвинуть его на 3 клетки, нам придется 3 раза повторить команду перемещения-step = 0 step += 1 print(f"Шаг ,в сделано: {step}’) # Вывод: Шагов сделано: J step += 1 print(f‘‘Шагов сделано: {step} ) # Вывод: Шагов сделано 2 step 4-= 1 print(fuiaroB сделана: {step}') # Вывод: Шагов сделано: 3
Но что, если нужно переместить робота на 100 или даже 1000 клеток? Повторять одну и ту же команду столько раз достаточно утомительно. Именно для таких ситуаций в программировании используются циклы - специатьные конструкции, позволяющие многократно повторять определенный фрагмент кода, называемый телом цикла. Они существенно упрощают решение задач, связанных с многократным выполнением одинаковых действий.
Цикл со счетчиком for и функция range()
Цикл for предназначен для перебора элементов в последовательности, поэтому чаще всего его используют в сочетании со встроенной функцией range(), генерирующую последовательность целых чисел:
for4 переменная in range(cTapT, стоп, шаг):
тело_цикла
Здесь переменная - это счетчик, который па каждой итерации цикла принимает следующее значение из последовательности, генерируемой функцией
Основы Python 112
range(). Внутри блока тело_цикла находятся инструкции, которые выполняются указанное количество раз.
Термин «итерация» произошёл от латинского слова iteratio, означающего «повторение». Поэтому итерация подразумевает процесс повторного выполнения одной и той же операции. Например, если вы готовите омлет и взбиваете яйца венчиком, то каждое движение венчика - одна итерация процесса взбивания.
Функция range()
Функция range() возвращает объект, создающий последовательность целых чисел по требованию (в цикле for).
Функция	range(start, stop, step)
Описание	Возвращает последовательность целых чисел о г start до stop (не включительно) с шагом step • stop - число, на котором последовательность заканчивается При этом само это значение в последовательность не входит
Параметры	Необязательные параметры: •	start - число, с которого начинается последовательность, по умолчанию start=0 •	step - число, задающее шаг изменения последовательности, по умолчанию step=l
Возвращаемое значение	Объект, создающий последовательность целых чисел
При указании одного обязательного параметра stop функция range(stop) создаёт последовательность чисел от 0 до stop - 1:
for i in range(30): print(i, end= )
# Вывод: 0 12 3 4 5 6 7 8 9
Основы Python 113
При указании двух параметров start и stop функция range(start, stop) создаёт последовательность чисел от start до stop - 1
for i in range(2, 8): print(i, end=1 ")
#	Вывод: 2 3 4 5 6 7
При указании всех трёх параметров start, stop и step функция range(start, stop, step) создаёт последовательность чисел от start до stop -1 с шагом step
	for i in range(2, 11, 2): print(i, end=" ")
#	Вывод: 2 4 6 8 10
Обратите внимание, что число stop не входит в последовательность чисел, создаваемую функцией range(). в то время как с числа start эта последовательность начинается.
Все параметры функции range() должны быть целыми числами При этом они могут быть как положительными, так и отрицательными:
	for i in range(-G, 9, 2): print(i, end=" )
#	Вывод: -6 -4-202468
Если шаг step отрицательный, то создается убывающая последовагель-ность, поэтому важно соблюдать условие start > stop:
for i in range(5, 0, -1): print(i, end=" ")
#	Вывод: 5 4 3 2 1
Перебор чисел из последовательности в цикле for
Теперь, используя цикл for и функцию range(), перепишем программу управления роботом и переместим его сразу на 5 шагов:
for 1 in range(l, 6):
print(f"UaroB сделано; {i}")
В результате на экран 5 раз будет выведено сообщение о количестве сделанных шагов без необходимости 5 раз писать функцию print()'
Основы Python 114
Шагов	сделано:	1
Шагов	сделано:	2
Шагов	сделано:	3
Шагов	сделано:	4
Шагов	сделано:	5
Здесь вывод каждой строки на экран соответствует одному выполнению тела никла, то есть было совершено 5 итераций.
Цикл с предусловием while
Цикл while обычно используется, когда заранее неизвестно точное количество необходимых повторений, но известно условие выхода из цикла:
while условие:
тело_цикла
Этот цикл также называют циклом с предусловием, так как он продолжает работу, пока условие истинно (True), и останавливается, когда оно становится ложным (False).
Давайте напишем программу, которая, как эхо, повторяет сообщения пользователя до тех пор, пока он не введет специальную фразу для завершения:
message = 1приТ("Скажите чт-т-нибудь: )
while message != "Хватит за мной повторять!":
print(message) message = input() print( Больше не буду’) # Ввод: Скажите что-нибудь: Привет! # Вывод: Привет!
#	Ввод: Скажите что-нибудь. Как дела?
#	Вывод: Как дела3
#	Ввод: Скажите что-нибудь: Хватит за мной повторять!
#	Вывод: Билыие не буду
В этом примере программа запрашивает у пользователя ввод строки. До тех пор, пока введённая строка не совпадает со строкой "Хватит за мной повторять! ", программа выводит на экран введенное сообщение и снова просит ввести новую строку.
Бесконечный цикл while
Важно обеспечить выполнимость условия в цикле while, так как иначе он будет выполняться бесконечно, что может привести к зависанию программы:
i = е
while i >= 3:
Основы Python 115
i += 1 print(i) if Вывод: 1 ft Вывод: 2
Здесь переменная i увеличивается на каждом шаге, и поскольку она всегда будет больше или равна нулю, цикл никогда не остановится. Чтобы избежать таких ситуаций, важно правильно формулировать условие завершения цикла.
Однако бесконечные циклы иногда бывают полезными, например, для ожидания определенного события или обработки потока данных.
Бесконечный цикл можно специально создать с помощью конструкции while True: while True: рг-1пТ("Я хочу сыграть с юбой в игру...")
#	Вывод: Я хочу сыграть с тобой в игру... it Вывод: Я хочу сыграть с тобой в игру...
Этот цикл будет бесконечно выводить сообщение на экран. Однако, на практике в таких циклах обычно предусматривается условие выхода с помощью оператора прерывания break.
Оператор прерывания цикла break
Оператор break используется для досрочного завершения цикла, независимо от того, были ли обработаны все элементы последовательности или выполнилось ли условие завершения самого цикла. Часто оператор break применяют в бесконечных циклах while, чтобы обеспечить выход из цикла при наступлении некоторого события.
Давайте рассмоэрим игру, в которой первый пользователь загадывает число, а второй пытается его угадать. Игра заканчивается, когда число угадано: answer = int(input("Загадай число: )) steps = 0 while True: steps += 1 guess = int(input("nonpo6yA угадать число: )) if guess < answer:
print( Загаданное число больше! ) elif guess > answer:
print( Загаданное число меньше! ) else:
print( Поздравляем! Вы выиграли! )
Основы Python 116
break
print(f "Количеств,. попыток: {steps}’)
#	Ввод: Загадай число: 20
#	Ввод: Попробуй угадать число: 5
it Вывод: Загаданное число больше!
#	Ввод: Попробуй угадать число: 10
#	Вывод: Поздравляем! Вы выиграли!
Здесь первый пользователь загадывает и вводит число, которое присваивается переменной answer. Переменная steps предназначена для подсчёта количества попыток.
В бесконечном цикле while True на каждой итерации второму пользователю предлагается ввести предположение guess Затем введённое число сравнивается с загаданным:
о Если guess < answer, то выводится подсказка "Загаданное число больше!".
о Если guess > answer, то выводился подсказка "Загаданное число меньше!".
о Если guess == answer, то выводится поздравление, и цикл досрочно завершается с помощью оператора break.
При выходе из цикла с помощью break дальнейшие инструкции внутри цикла не выполняются, и управление передается на первую строку кода, следующую за циклом.
Оператор пропуска итерации в цикле continue
Прервать можно не только весь цикл, но и текущую итерацию Для этого предназначен оператор continue, который позволяет пропустить оставшуюся часть текущего выполнения тела цикла и перейти к следующей итерации. Это может быть полезно, когда нужно пропустить определённые случаи и продолжить обработку оставшихся элементов.
Допустим, нам нужно вывести на экран все числа от 1 до 50. кроме тех, которые делятся на 2 или на 3 без остатка:
for i in range(lj 53):
if i % 2 == 0 or i % 3 == 0: continue print(i, end= )
#	Вывод: 1 5 7 11 13 17 19 23 25 29 31 35 37 41 43 47 49
Здесь в цикле for перебираются все числа от 1 до 50 включительно. Для
Основы Python 117
каждого числа проверяется, делится ли оно на 2 или на 3 без остатка:
о Если условие выполняется, текущая итерация прерывается оператором continue, и управление возвращается обратно в начало цикла для следующей итерации.
о Если число не делится без остатка на 2 или 3, оно выводится на экран.
Блок кода после завершения никла else
Циклы for и while в Python могут содержать необязательный блок else, который выполняется, если цикл был завершен естественным образом, а не прерван оператором break.
В цикле for блок else выполняется, если все элементы последовательности были обработаны:
for i in range(l, 4): print(i, end= ) else:
print( Цикл завершился") it Вывод: 12 3 Цикл завершился
В цикле while блок else выполняется, если условие стало ложным:
i = 1
while i < 4: print(i, end= ") i += 1
else:
print( Цикл завершился") it Вывос): 12 3 Цикл завершился
Вложенные циклы
Внутри одного цикла можно располагать другие циклы. Такие конструкции называются вложенными циклами и могут применяться для выполнения сложных задач.
Одним из классических примеров использования вложенных циклов является вывод таблицы умножения: for i in range(l, 10): for j in range(l, Ivi):
print(i * j, end="\t") printQ
Здесь внешний цикл проходит по строкам таблицы - по значениям переменной i. Для каждого значения i запускается внутренний цикл.
Основы Python 118
Внутренний цикл проходит по столбцам таблицы - по переменной j. Для каждого значения j вычисляется произведение i * j, которое выводится на экран.
В результате на экран будет выведена таблица умножения:
1	2	3	4	5	6	7	8	9
2	4	6	8	10	12	14	16	18
3	6	9	12	15	18	21	24	27
4	8	12	16	20	24	28	32	36
5	10	15	20	25	30	35	40	45
6	12	1К	24	30	36	42	48	54
7	14	21	28	35	42	49	56	63
8	16	24	32	40	48	56	64	72
9	18	27	36	45	54	63	72	81
Тот же результат можно получить с использованием вложенного цикла while:
i, j = 1, 1 while i < 10: while j < 10:
print(i * j, end» '\t )
j += 1
print() i += 1 j = 1
Здесь переменным i и j присваиваются начальные значения 1. Во внешнем цикле проверяется условие i < 10, и если оно выполняется, то запускается внутренний цикл.
Во внутреннем цикле проверяется условие j < 10, и если оно выполняется, вычисляется произведение i * j. которое выводится на экран.
После завершения внутреннего цикла, переменная i увеличивается на 1, а переменная j сбрасывается до 1 для новой строки. Снова проверяется условие i < 10
Уровень вложенности циклов в Python не ограничен, однако необходимо стараться минимизировать глубину вложенности там. где это возможно, так как её увеличение может негативно сказываться на производительности и читаемости кода.
Примеры
Пример 1. Обратный отсчет при старте ракеты
Программа отсчитывает время до запуска ракеты, а затем объявляет о
Основы Python 119
старте:
foe count in range(10, J, -1): print(count, end=", )
print( 'пуск!")
Вывод:
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, пуск!
Пример 2. Авторизация с ограниченным числом попыток
Система авторизации блокирует вход после 3 неудачных попыток ввода пароля:
#	Исходные данные
correct_password = "qwertyl23" # Правильный пароль attempts = 3 # Количество попыток
#	3 попытки в цикле whiLe while attempts > 0: password = input( “ведите пароль. ) if password == correct_password: print( Доступ разрешен ") break # Досрочное завершение цикла attempts -= 1 print(f"Неверно! Осталось попыток: {attempts}’) else: # Выполняется, если цикл не был прерван оператором break print( Доступ заблокирован. Обратитесь в поддержку.)
Вывод:
Введите пароль: qwerty
Неверно! Осталось попыток: 2
Введите пароль: qwertyl23
Доступ разрешён
Пример 3. Таблица символов Юникода
Программа для кодирования символов выводит на экран часть таблицы вывести часть таблицы символов Юникода, показывая числовой код и соответствующий символ для определенного диапазона:
#	Начинаем с 'А' (65) и заканчиваем после У (89) с шагом 5 символов
for row_start_code in range(65, 90, 5):
for offset in range(5): # 5 символов в каждой строке
current_code = row_start_code + offset
print(f’{current_code} '{chr(current_coae)} ", end="\t")
print() # Переход на новую строку после каждой группы из 5 символов
Основы Python 120
Вывод:
65:	'Д'	66:	‘В'	67:	•с	68:	'D'	69:	' Е ’
70:	' F '	71:	' G'	72:	'Н'	73:	•I'	74:	д*
75:	'К'	76:	' L'	77:	'М'	78:	'№	79:	'О'
80:	' Р'	81:	'Q'	82:	• R'	83:	'5'	84:	' Т'
85:	'U'	86:	'V	87:	'W'	88:	'X'	89:	' Y’
ИтО1 и
Цикл - это конструкция для многократного выполнения одного и того же фрагмента кода (тела цикла).
J Цикл for используется для перебора элементов в последовательности. Его часто применяют с функцией range(), которая генерирует последовательность целых чисел.
Цикл while используется, когда точное количество повторений заранее неизвестно. но есть условие выхода из цикла.
S Конструкция while True создаёт бесконечный цикл.
J Оператор break используется для досрочного завершения цикла.
J Оператор continue используется для пропуска оставшейся части текущей итерации цикла.
Блок else после циклов for и while выполняется, если цикл был завершён естественным образом (для for - все элементы обработаны, для while -условие стало ложным).
Вложенные никлы - это циклы, расположенные внутри других циклов.
Задания для самопроверки
1.	Выведите на экран следующие последовательности чисел:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
10, 14, 18, 22, 26
20, 19, 18, 17, 16, 15
2.	Запросите у пользователя целое число n > 1 и выведите на экран наименьшее число, на которое оно делится без остатка и отличное от единицы.
Пример входных данных Пример выходных данных
17	17
123	3
55	5
3.	Запрашивайте у пользователя ввод чисел до тех нор. пока не будет
Основы Python 121
введено первая строка, не являющаяся числом. После этого вычислите и выведите на экран сумму введённых чисел.
Пример входных данных Пример выходных данных 1	6
2 3 Нет 20	50
21 9 Хватит 100	300
200 Стоп
4.	Запрашивайте у пользователя ввод чисел до тех пор. пока не будет введено первое нечётное число. После этого выведите на экран последнее введённое число. Если первое введённое число было нечётным, то выведите 0.
Пример входных данных Пример выходных данных 2	4
4 7 20	22
22 25 1	0
5.	Запросите у пользователя целое число n > 1 и выведите на экран на одной строке через пробел все нечётные числа от 1 до п включительно, кроме чисел от 11 до 21 включительно.
Пример входных данных	Пример выходных	данных
34	1	3	5	7	9	11	21	23	25	27	29 31 33
14	1	3	5	7	9
25	1	3	5	7	9	23	25
6.	Запросите у пользователя целое число n > 1 и строку s. Выведите на экран треугольник с высотой п из строк s.
Основы Python 122
Пример входных данных Пример выходных данных
3	*
*	* ж
	* ж *
5	#
#	# #
	# # it
	# # # #
	it # it it it
6	снег
снег	снег снег
	снег снег снег
	снег снег снег снег
	снег снег снег снег снег
снег снег снег снег снег снег
2.12 ОШИБКИ И ИСКЛЮЧЕНИЯ
Ошибки в программировании неизбежны, однако важно научиться распознавать и грамотно реагировать на них. При одних ошибках код вовсе не запустится. а при других аварийно завершится при выполнении строки с ошибкой. Однако Python - очень дружелюбный язык, и всегда старается подсказать, в чем проблема.
Синтаксические ошибки
Самый частый гость у начинающих программистов - это синтаксические ошибки, например, опечатки или пропущенные символы. Python в этом плане очень строгий и требует, чтобы код был написан по определенным правилам. Если вы допустите такую ошибку. Python даже не запустит вашу программу, а сразу сообщит, что именно ему не понравилось.
Например, выведем сообщение на экран, но пропустим закрывающую скобку при вызове функции print():
print( Эта инструкция написана не ->чень правильна
Запустив этот код, вы увидггтс примерно такое сообщение о синтаксической ошибке:
Traceback (most recent call last):
File "/home/irina/projects/task.py", line 2
Основы Python 123
print("3Ta инструкция написана не очень правильно"
SyntaxError: '(' was never closed
Python выводит трассировку (англ, traceback) с информацией не только о самой ошибке, но и о её месте. Так line 2 означает, что исключение вызвал код в строке 2.
Исключения
Но бывают ошибки другого рода - те, которые возникают уже во время работы программы. Представьте, что ваша программа должна разделить одно число на другое, но второе число оказалось нулем. В математике на ноль делить нельзя, и Python тоже об этом знает. Такие ошибки, возникающие в процессе выполнения программы, называются исключениями.
Давайте напишем программу, которая запрашивает у пользователя два числа и выводит на экран результат деления первого числа на второе:
numberl = float(input("Пожалуйста, введите первое число ))
number’2 = float(input( Пожалуйста, введите второе число: ))
result = numberl / number2
print(f"{numberl} / {riumber2} = {result} )
Допустим, пользователь ввёл числа 1 и 2, тогда программа завершится без ошибок.
Пожалуйста, введите первое число: 1
Пожалуйста, сведите второе число: 2 1 / 2 = 0.5
Но если в качестве второго числа был введён 0. то выполнение программы прервётся на строке result = numberl / number2 и будет вызвано исключение ZeroDivisionError:
Пожалуйста, введите первое число: 1
Пожалуйста, введите второе число: 0
Traceback (most recent call last):
File "/home/irina/toss a coin/Архив/Учебник.ру", line 3, in <module> result = numberl / number2
ZeroDivisionError: float division by zero
Эта же программа может прерваться и из-за другого исключения, если пользователь вместо числа введёт строку, которую невозможно преобразовать в число:
Пожалуйста, введите первое число: Цифра 1 Traceback (most recent call last):_________
Основы Python 124
File “/home/irina/toss a coin/Архив/Учебник.ру", line 1, in <module> numberl = 1п1(1при1("Пожалуйста, введите первое число: "))
А АЛ АЛААЛААЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛЛ
ValueError: invalid literal for int() with base 10: 'Цифра 1‘
Так как строку "Цифра 1" нельзя преобразовать в целое число, выполнение программы прерывается на первой же строке и вызывается исключение ValueError.
Типы исключений
В ходе работы программы могут возникать самые разные ошибки, для которых Python предоставляет большой набор встроенных исключений. Также существует возможность разработки собственных исключений, но это уже более продвинутый уровень владения Python.
Давайте рассмотрим некоторые стандартные исключения.
1.	NameError (с англ. - ошибка имени) - используется объект (переменная или функция), который еще не были создан.
print(username)
# Ошибка: NameError: name 'username‘ is not defined
Здесь переменной name еще не было присвоено значение, поэтому её нельзя вывести на экран.
2.	ТуреЕггог (с англ. - ошибка типа) - операция применяется к объекту не того типа:
"10 + 10
# Ошибка: ТуреЕггог: can onLy concatenate str (not "int") to str
Здесь следует вспомнить, что Python - язык с сильной типизацией, поэтому строки и числа нельзя складывать без предварительного преобразования
3.	ValueError (с англ. - ошибка значения) - операция применяется к объекту правильного типа, но недопустимого значения:
int("Число 25’ )
# Ошибка: vaLueError: invaLid Literal, for int() with base 10: 'Число'
Здесь строку "Число 25" невозможно преобразовать в число, так как она содержит недопустимые буквенные символы.
4.	ZeroDivisionError (с англ. - ошибка деления на ноль) - деление на ноль: result = 10.5 / 0
# Ошибка: ZeroDivisionError: float division by zero
Здесь всё совсем как в математике: на ноль делить нель зя.
Основы Python 125
Обработка исключений
Обычно мы можем предположить, какие исключения могут возникнуть в ходе выполнения программы. Например, пользователь не обязательно введёт число или захочет поделить на ноль. Избежать аварийного завершения программы в таких случаях позволяет специальная конструкция try/except/else/f i-nally способная перехватывать и обрабатывать исключения:
try:
действия_которые_могут_привести_к_исключению except ГипИсключения.
дейстьия_если_возникло_исключение else:
действия_если_не_возникло_исключение
finally:
действия_которые_будут_вь1Полнены_в__люЬом_случае
Если код внутри блока try вызвал исключение, тип которого совпадает с исключением, указанным после оператора except, то выполнение блока try останавливается. и контроль переходит к соответствующему блоку except. При этом количество блоков except не ограничено и можно перехватывать разные типы исключений.
Кроме этого, данная конструкция включает в себя необязательные блоки else и finally Блок else выполняется только в том случае, если код в блоке try был выполнен без исключений. А блок finally выполняется всегда, независимо от того, было ли исключение.
Как мы уже убедились, деление на ноль приводит к исключению ZeroDi-visionError. а преобразование в число строки в неправильном формате - к исключению ValueError. Поэтому давайте перепишем пример программы для деления двух чисел, используя try/except и данные типы исключений:
try:
number! = int(input("Пожалуйста, введите первое целое число: )) number? = int(input("Пожалуйста, введите второе целое число: )) result = numberl I number?
print(f{numberl} I {number?} = {result} )
except ZeroDivisionError:
prinr( На ноль делить нельзя!')
except ValueError:
print( Пожалуйста, .-ведите целое число!”)
Теперь если пользователь захочет поделить на ноль, программа всё равно продолжит работу:
Пожалуйста, введите первое число: 1
Основы Python 126
Пожалуйста, «ведите второе число: 0 На ноль делить нельзя!
Также такой код обрабатывает ввод некорректного значения и исключение ValueError:
Пожалуйста, введите первое целое число: пять Пожалуйста, введите целое число!
В одном блоке except можно обрабатывать сразу несколько типов исключений. Для этого достаточно перечислить их в скобках через запятую:
try:
number! = 1nt(input("Пожалуйста, введите первое число: ))
number? = int(input("Пожалуйста, введите второе число: ))
result = numberi/number2
print(f’’{numberl} / {number?} = {result}")
except (ValueError, ZeroDivisionError): print("ync! Что-то пошло не так!')
И хотя явное указание типов ожидаемых исключений является хорошей практикой, это не является обязательным:
try:
numberl = ^1(1при^"Пожалуйста, введите первое число: "))
number? = int(input("Пожалуйста, введите второе число. )) result = nurnberl/number?
print(f"{numberl} I {number?} = {result} ) except:
print(’ync! 4i >-to пошло не так!")
В таком случае блок except обрабатывает все возникающие исключения. При делении на ноль мы увидим сообщение "Упс! Что-то пошло не так!":
Пожалуйста, введите первое число: 1 Пожалуйста, введите второе число: 0 Упс! Что-то пошло не так!
Такое же, как и при вводе значения, не являющегося числом:
Пожалуйста, «ведите первое число: пять Упс! Что-то пошло не так!
Но для того, чтобы понимать, какое исключение было перехвачено, с помощью конструкции Exception as е можно сохранить информацию об исключении в переменную е:
try:
numberl = int(input("Пожалуйста, введите первое число: )) number? = int(input("Пожалуйста, введите второе число: )) result = nurnberl/number?
print(f"{numberl} I {number?} = {result}")
except Exception as e:
Основы Python 127
print(f Упс! Что -то пошло не так: {е}')
Значение переменной е может быть выведено на экран:
Пожалуйста, введите первое число: 1
Пожалуйста, введите второе число: 0
Упс! Что-то пошло не так: division by zero
Хотя этот подход гарантирует, что программа не выйдет из строя ни при каких ошибках, его следует использовать с осторожностью, чтобы не замаскировать критичные ошибки.
Необязательный блок else
Иногда может понадобиться выполнить код только в том случае, если блок try завершился корректно, то есть не произошло исключений. Для этих целей предназначен необязательный блок else:
try:
numberl = int(input("Пожалуйста, введите первое число: ))
number? = 1пс(1приТ("Пожалуйста, введите второе число: )) result = numberl/number2
print(f"{numberl} / {number?} = {result}")
except ValueError:
print( Вы должны ввести число!")
except ZeroDivislonError:
print(f"Ha ноль делить нельзя!")
else:
рг1п1(Г'Увеличим результат деления в 101’0 раз: {result * 1000}')
Здесь результат деления будет увеличен в 1000 раз, если код в блоке try был выполнен полностью:
Пожалуйста, введите первое число: 1
Пожалуйста, введите второе число: 2 1 / 2 = 0.5
Увеличим результат деления в 1000 раз: 500.0
Но если возникло исключение и был выполнен код из блока except, то код в блоке else игнорируется:
Пожалуйста, введите первое число: 1 Пожалуйста, введите второе число: 0 На ноль делить нельзя!
Такая структура позволяет не помешать всю логику в блок try.
Необязательный блок finally
Независимо от того, возникли ли исключения, иногда нужно гарантированно выполнить некоторые операции (например, закрыть файл). Для этого Основы Python 128
служит дополнительный блок finally, который выполняется всегда, независимо от того, произошло исключение в блоке try или нет.
try:
numberl = int(input("Пожалуйста, введите первое число: ))
number? = int(input("Пожалуйста, введите второе чмкло: ))
result = numberl/number2
print(f"{numberl} / {number?} = {result} )
except ValueError:
рг!пт(’Вы должны ввести число’")
except ZeroDivisionError:
print(f"Ha ноль делить нельзя!")
else:
рг1пТ(Г'Увеличим результат деления в 1000 раз: (result * 10Р0} ) finally:
print( Спасибо за использование нашего калькулятора!")
Сообщение из блока finally будет выведено на экран, как в случае корректной работы программы и отсутствия исключений:
Пожалуйста, введите первое число: 1 Пожалуйста, введите второе число: ?
1 / ? = 0.S
Спасибо за использование нашего калькулятора!
Так и если было перехвачено какое-то исключение:
Пожалуйста, введите первое число: 1
Пожалуйста, введите второе число: 0
На ноль делить нельзя!
Спасибо за использование нашего калькулятора!
Принудительный вызов исключений
Иногда возникает необходимость принудительно вызвать исключение в коде. Это может быть нужно, например, для оповещения о недопустимом введённых данных, или же для повторной генерации уже пойманного исключения. Для этой цели в Python используется оператор raise:
raise ТипИсключения("Сообщение об ошибке )
После ключевого слова raise указывается тип вызываемого исключения, например. ValueError или ТуреЕггог, после которого в скобках может быть указана строка с сообщением об ошибке.
Например, мы можем вызывать исключение при вводе отрицательного числа:
number = int(input("Введите число: )) if number < 0:
#	Вызываем исключение; если число отрицательное
Основы Python 129
raise ValueError('Число должнг быть положительным!")
Если мы запустим этот код и введём отрицательное число, например, -10, то это приведет к аварийному завершению программы с трассировкой, указывающей на наше принудительно вызванное исключение:
введите число: -10
Traceback (most recent call last):
File "/home/irina/euthymia/test.py", line 4, in <module> raise ValueError("4noio должно быть положительным!")
ValueError: Число должно быть положительным!
Как видите, это выглядит точно так же, как и любое другое исключение, автоматически вызванное Python.
Также оператор raise часто используется внутри блока except для повторного вызова уже перехваченною исключения. Это полезно, когда мы хотим выполнить какие-то действия дотированию (записи информации) ошибки в блоке except, но при этом всё равно хотим вызвать соответствующее исключение:
number = int(input( введите число: )) try:
result = 10 / number
except ZeroDivisionError:
print( Была перехвачена попытка делении на ноль")
#	Мы обработали исключение, но хотим, чтобы оно пошло дальше raise
Если пользователь введёт число 0. то будет выведено сообщение о перехвате попытки деления на ноль, после чего вызвано исключение ZeroDivisionError
Введите число: 0
Была перехвачена попытка деления на ноль
Traceback (most recent call last):
File "/home/irina/euthymia/test.py", line 3, in <module> result = 10 / number
ZeroDivisionError: division by zero
Без использования оператора raise программа бы завершилась корректно и просто было выведена строка с уведомлением об ошибке.
Рекомендации но обработке исключений
Обработка исключений - это важный навык, который делает ваши программы более надежными и устойчивыми к неожиданным ситуациям. Не бойтесь ошибок, а учитесь их предвидеть и обрабатывать, для чего постарайтесь
Основы Python 130
следовать следующим рекомендациям:
1.	Перехватывайте только ожидаемые исключения - не обрабатывайте все возникающие исключения одним способом без необходимости, так как это может скрыть неожиданные ошибки.
2.	Сообщайте об ошибках понятно - сообщения об ошибках должны быть информативными для пользователя или программиста, чтобы они понимали. что пошло не так и. как это исправить.
3.	Не злоупотребляйте обработкой исключений - используйте обработку исключений только там. где это действительно необходимо. Не стоит оборачивать в try весь написанный код.
4.	Делайте блоки try краткими - чем меньше кода находится внутри блока try. тем легче понять, какая именно операция могла вызвать исключение.
Примеры
Пример I. Выбор пункта меню в программе
Пользователю предлагается выбрать пункт меню, введя его номер. Программа корректно обрабатывает ситуацию, если пользователь вводит текст вместо числа или оставляет поле пустым:
print( Выберите пункт меню")
print( 1. Показать новости")
print( 2. Просмотреть профиль")
print("3. ьыйти’)
menu_choice = 0 # Переменная для хранения выбора пользователя
while True:
try:
choice_str = input( ведите номер пункта меню: ")
#	Попытка преобразовать ввод в целое число
menu_choice = int(choice_str)
except ValueError:
#	Обрабатываем, если строка не может быть преобразована в число
prinv( Ошибка: Вы должны ввести номер пункта (1, 2 или 3)') else:
#	Если ввод успешно преобразован 6 число, проверяем его диапазон
if 1 <= menu_chuice <= 3:
print(f"Bbi выбрали пункт {menu_choice} ) break # Выходим из цикла, если выбор корректен else:
print( Ошибка: Такого пункта нет. Введите 1, 2 или 3")
Finally:
#	Выводится после каждой попытки выбора
print("Попытка выбора пункта меню завершена")
Основы Python 131
print( 'Программа продолжает работу с вашим выбором1)
Вывод:
Выберите пункт меню
1.	Показать новости
2.	Просмотреть профиль
3.	Выйти
введите номер пункта меню: номер?
Ошибка: Вы должны ввести номер пункта (1, 2 или 3)
Попытка выбора пункта меню завершена.
Введите номер пункта меню: 1
Вы выбрали пункт 1
Попытка выбора пункта меню завершена
Программа продолжает работу с вашим еыбором
Пр	имер 2. Расчёт среднею балла
Программа запрашивает баллы по трём предметам. Если пользователь вводит нечисловое значение для любого из баллов, программа сообщает об ошибке и просит ввести баллы заново.
while True:
try:
print( Пожалуйста, введите баллы:")
scorel_str = input("Балл за первый предмет: ) scorel = int(scorel_str)
score2_str = input( 'Балл за второй предмет: ") score2 = int(score2_str)
score3_str = input("Балл за третий предмет; ) зсогеЗ = int(score3_str)
#	Если все баллы успешно преобразованы
average_score = (scorel + score2 + score3) I 3 printC ==========================' ) print(f"Средний балл: {average_score:.2f}') break # Уыходил из цикла, если все баллы введены корректно
except ValueError:
#	Обрабатываем, если балл не является целым числом print( Ошибка; Все баллы должны быть целыми числами") except Exception as е:
#	Обрабатываем любые другие неожиданные ошибки print(f"Произошла непредвиденная ошибка: (е) ) finally:
#	Этот блок выполняется после каждой попытки ввода баллов print( Попытка ввода баллов завершена")
Основы Python 132
Вывод:
Пожалуйста, введите баллы: Балл за первый предмет: 97 Балл за второй предмет: 84 Балл за третий предмет: 78
Средний балл: 86.33
Попытка ввода баллов завершена
И то1 и
J Синтаксические ошибки - это ошибки, возникающие из-за нарушений правил языка (опечатки, пропущенные символы). Код, содержащий синтаксические ошибки, не будет запущен.
S Исключении - это ошибки, возникающие во время выполнения программы, приводящие к её аварийному завершению.
J Конструкция try/except перехватывает и обрабатывает исключения, предотвращая аварийное завершение программы. Код, который может вызвать исключение, помешается в блок try. Если исключение возникает, управление передаётся в блок except.
Необязательный блок else после try/except выполняется только в том случае. если код в блоке try был выполнен без исключений.
J Необязательный блок finally после try/except выполняется всегда, независимо от того, было ли исключение.
Ключевое слово raise позволяет принудительно вызвать исключение.
Задания для самопроверки
1.	Найдите и исправьте ошибки в следующем коде:
age = int(input("Сколько тебе лет? )
if age >= 18
print( 'Tbi совершеннолетний и можешь голосовать) else:
print("Tw ещё несовершеннолетний, тебе нужно подождать")
2.	Рассмотрите следующую трассировку ошибки:
Traceback (most recent call last):
File "/home/user/script.py", line 5, in calculate result = numberl I number2
ZeroDivisionError: division by zero
На какой строке кода возникло исключение? Что является причиной
Основы Python 133
возникновения ошибки?
3.	Запросите у пользователя строку и преобразуйте её в целое число. Если пользователь ввёл не число, то выведите на экран строку "Не удалось преобразовать в число".
Пример входных данных Пример	выходных	данных
1	1
Число 2	Не удалось	преобразовать	в число
-12	-12
4.	В каком случае будет выполнен блок else в конструкции try/ex-cept/else?
5.	Запросите у пользователя возраст, и если возраст больше или равен 16. то выведите сообщение "Доступ разрешён", иначе - "Доступ запрещён". Если введённую строку нельзя преобразовать в число, то выведите сообщение " Введённое значение должно быть целым числом". После обработки ввода, независимо от того, было ли введенное значение корректным числом или нет, выведите сообщение "Проверка возраста завершена".
Пример входных дан- Пример выходных данных ных
16	Доступ разрешён
Проверка возраста завершена
14	Доступ запрещён
Проверка зозраста завершена
Всегда восемнадцать	Введённое значение должно быть целым числом
Проверка возраста завершена
Основы Python 134
1 лава 3
Коллекции
3.1 ВВЕДЕНИЕ В КОЛЛЕКЦИИ
Встроенные пипы данных в Python позволяю)’ эффективно работать с информацией различного формата. И если числовые и логические типы предназначены для хранения отдельных значений, то коллекции служат контейнерами для одновременного хранения множества элементов.
Коллекции в Python представлены такими типами данных, как уже хорошо знакомые нам строки, а также списки, кортежи, множества и словари.
Каждая встроенная коллекция обладает уникальным набором свойств, определяемых её упорядоченностью, изменяемостью и требованиями к уникальности элементов. Выбор подходящей коллекции зависит от конкретной задачи и требований к хранению и обработке данных.
Таблица 10 - Свойства основных встроенных типов данных в Python
Назва-	Пример	Упорядо-	Изме-	Уникаль-
ние		ченность	няе- мость	ность
Строки	"Александр'’	О	е	
Списки	["Хлеб", "Рыба", "Квас"]	о	о	
Кортежи	("Математика", "Физика")	о	о	о
Множества Словари	{1, 2, 3, 4, 5} {"Россия": "Москва",	о	о	о Ключи
	"Китай": "Пекин"}	о	о	Значе- НИЯ
Коллекции 135
Основные коллекции
Давайте кратко рассмотрим каждую из встроенных коллекций, представленных в Python.
1.	Строки, которые мы уже неоднократно использовали, представляют собой упорядоченную последовательность символов, заключённую в двойные " или одинарные ' кавычки: username = "Sauron999"
Строки неизменяемы, то есть мы не можем изменить отдельные символы в строке после её создания. Поэтому все операции над строками на самом деле создают новую строку.
2.	Списки - наиболее гибкая и часто используемая коллекция, которая содержит упорядоченный набор элементов в квадратных скобках: books = ["Хоббит, или Туда и обратно", "Две крепости", "Братство Кольца"]
Списки изменяемы, поэтому мы можем свободно добавлять, удалять и изменять их элементы.
3.	Кортежи, как и списки, хранят элементы в определённом порядке, однако они неизменяемы и создаются с помощью круглых скобок: hobbits = ("Фродо Бэггинс", "Бильбо Бэггинс")
После создания кортежа в него невозможно внести какие-либо изменения, что делает его полезным для хранения данных, которые не должны подвергнуться случайным изменениям, например, какие-то константы.
4.	Множества в Python представляют собой неупорядоченную коллекцию уникальных элементов. Они во многом напоминают математические множества и определяются путем перечисления элементов в фигурных скобках:
regions = {"Хообит-jH ", "Тростниковая Топь ’, "Иглсушко', "Малс-оийка")
Множества являются изменяемыми, что позволяет добавлять, изменять и удалять элементы.
5.	Словари в Python очень похожи на словари в реальной жизни, и организуют данные в виде пар «ключ-значение», заключённых в фигурные скобках:
heroes = { Арагорн": 2931, "Леголас": 3018, "Бард I": 2941}
Ключи словаря должны быть уникальными, в то время как значения
Коллекции 136
мснуг повторяться. Словари изменяемы, что позволяет добавлять, удалять и изменять пары «ключ-значение». Исторически словари считались неупорядоченными коллекциями. Однако начиная с Python 3.7, словари гарантированно сохраняют порядок вставки элементов.
Упорядоченность коллекций
Строки, списки, кортежи и словари обладают свойством упорядоченности, то есть порядок, в котором элементы были добавлены в коллекцию, сохраняется.
При этом в строках, списках и словарях каждый элемент имеет свой порядковый номер, называемый индексом, по которому можно получить этот элемент. Для этого следует указать индекс в квадратных скобках:
sport = [’Теннис , "Футбол", "Волейбол"]
print(sport[l])
#	Вывод: Футбол
В программировании принято начинать считать с 0, поэтому первый элемент "Теннис" находится по индексу 0, второй элемент "Футбол" - по индексу 1 и так далее. Более подробно механизм индексов мы рассмотрим в следующем napai рафе.
В Python, как и во многих других языках программирования, индексация начинается с 0, а не с 1, так как индекс элемента представляет собой его смещение в памяти относительно начала массива. Первый элемент находится ровно в начале структуры данных, поэтому его смещение равно 0, второй элемент - со смещением 1 и так далее. Такой подход упрощает адресацию и согласуется с низкоуровневыми принципами работы памяти в языках программирования.
Элементы словаря пусть и сохраняют порядок вставки элементов, но к ним нельзя обратиться по индексу. Вместо него в квадратных скобках указывается ключ словаря, по которому требуется получить значение:
plans = {"Суббота": "Сделать уборку", "Воскресенье": "Спать"}
Коллекции 137
print(plans[ Воскресенье"]) # Вывод: Спать
Изменяемость коллекций
Списки, словари и множества предоставляют возможность добавлять, удалять и изменять элементы после создания.
Например, изменим элемент списка по индексу:
berries = ["Голубика", "Малина", 'Ежевика']
berries[0] = "Клюква" print(berries)
#	Вывод: ["клюква", "Малина", "Ежевика"]
Или значение словаря по ключу:
poems = {"Гомер": "Одиссея", ’Вергилий": "Энеида }
puems[ Гомер'] = "Илиада’ print(poems)
#	Вывод: {'Гомер': 'Илиада', 'Вергилий': 'Энеида'}
Однако изменение по индексу элемента строки или кортежа приведет к исключению ТуреЕггог.
Например, если мы хотим изменить строку "Вера" на "Лера", изменив элемент с индексом 0 на букву "Л", то это приведёт к ошибке:
name = Вера" гате[0] = "Л" # Ошибка: ТуреЕггог: 'str' object does not support item assignment
Это же исключение вызывается при попытке изменения элемента кортежа:
populations = (300000, 100000, ЗБОйЗг’) populations[1] = 420000
#	Ошибка: ТуреЕггог: 'tupLe' object does not support item assignment
Но если кортеж содержит изменяемые объекты, такие как списки, то сами эти объекты могут быть изменены, хотя сам кортеж останется неизменным:
countries = ('Россия”, "Китай", ["Италия , "Франция"])
countries[2][1] = ’Испания" print(countries)
#	Вывод: ("Россия", "Китай", ["Италия", "Испания"])
Уникальнос ть коллекций
Множества является единственной встроенной коллекцией, которая гарантирует хранение только уникальных элементов. При добавлении уже существующего элемента, он будет автоматически проигнорирован:
Коллекции 138
numbers = {1, 2, 2, 3, 3, 3} print(numbers)
#	Вывод- fl, 2, 3}
Требование уникальности также распространяется на ключи словарей Каждый ключ в словаре должен быть уникальным. При добавлении элемента с уже существующим ключом, старое значение будет перезаписано:
drinks = { Сок": 2, "Чай": 1, "Морс": 3}
drinks["C0K ] = 1
print(drinks)
#	Вывод: {'Сок': 1, 'Чай': 1, 'Морс': 3}
Примеры
Пример 1. Хранение координаты юрода
Геосервис хранит неизменяемые координаты городов в виде кортежа:
rroscow_cujrdinates = (55.7558, 37.6176) # Координаты Москвы
print (f"Москва: широта: (mos<:ow_coordinates[0]}, долгота: {moscow_coordi-nates[l]} )
Вывод:
Москва: широта: 55.7558, долгота: 37.6176
Пример 2. Список товаров в ин 1ернет-ма1 азине
В интернет-магазине список популярных товаров в разделе «Смартфоны» хранится в виде списка, однако после обновления ассортимента модель «Samsung Galaxy S23 Ultra» уступила свое место «Sony Xperia 1 VI»:
#	Список моделей смартфоноф
smartphones = [
"iPhone 14 Pro Max",
"Samsung Galaxy S23 Ultra',
"Xiaomi Mi Mix Fold 2",
"Google Pixel 7 Pro",
]
#	Второй элемент находится no индексу 1
smart phones[1] = "Sony Xperia 1 VI"
print(smartphones)
Вывод:
['iPhone 14 Pro Max', 'Sony Xperia 1 VI', 'Xiaomi Mi Mix Fold 2', 'Google Pixel 7 Pro']
Коллекции 139
Пример 3. Обработка заказа клиента
Если в интернет-магазине клиент поставил галочку напротив пункта «Доставка», то к общей цене прибавляется стоимость доставки:
#	Данные по заказу order_items = {
"Название товара': ‘ iPhone 14 Pro Max' ,
"Количество": 1,
"цена": 12U00B,
"Доставка": True }
#	Проверяем, нужна ли доставка
if order_items["Доставка J:
order_items["Цена J += 500
print(f"K оплате: {огЦег_11ет$["Цена ]} руб.")
Вывод:
К оплате: 120500 руб.
I1T01 и
J Коллекции - это контейнеры для хранения множества элементов. Они отличаются по упорядоченности, изменяемости и требованиями к уникальности элементов.
J Строки - это упорядоченные и неизменяемые последовательности символов, заключённая в двойные (") или одинарные (') кавычки:
Списки - это упорядоченные и изменяемые последовательности элементов в квадратных скобках.
J Кортежи - это упорядоченные и неизменяемые последовательности элементов в круглых скобках.
J Множества - это неупорядоченные и изменяемые наборы уникальных элементов в фигурных скобках.
J Словари - это упорядоченные и изменяемые последовательности пар «ключ-значение» в фигурных скобках.
Элемент строки, списка и кортежа можно получить по порядковому номеру, называемому индексом. При этом в программировании принято начинать считать с 0.
Коллекции 140
Задания для самопроверки
1	Какая коллекция предназначена для хранения упорядоченной последовательности элементов, которые не могут быть изменены после создания?
2	. Сколько элементов будет в множестве, созданном из элементов списка numbers = [1, 2, 2, 3, 3, 3, 4] и почему?
3	. Какие коллекции сохраняют порядок элементов?
4	. Представьте, что вам нужно хранить список уникальных идентификаторов пользователей, при этом порядок не имеет значения. Какой тип коллекции будет наиболее подходящим и почему?
5	Что произойдет, если в строке "кора" попытаться изменить символ "к" па символ "н"?
3	.2 ИНДЕКСАЦИЯ И СРЕЗЫ
Строки, списки и кортежи являются упорядоченными коллекциями, то есть их элементы имеют фиксированный порядок и свои собственные порядковые номера, называемые индексами С их помощью можно как получать отдельные элементы коллекции, так и извлекать целые последовательности элементов.
Получение элементов по индексу
По индексу можно получить отдельные элементы строк, списков и кортежей. Например, у нас есть следующие переменные:
message_str = Как дела?" # Строка
shopping list = [ 'Еда 1 , 'Еда 2", "Еда 3', "Еда 4", "Еда 5"] # Список friends_tuple = ( 'Друг 1 , "Друг 2", "Друг 3") # Кортеж
Первый символ "К" строки "Как дела?" находится по индексу 0, второй символ "а" - по индексу 1, и так далее.
При этом можно использовать не только положительные, но отрицатель' ные индексы, которые позволяют обращаться к элементам с конца коллекции. В таком случае последний элемент "Друг 3" кортежа friends имеет индекс -1, предпоследний - -2, и так далее.
Коллекции 141
012345678
ан дела?
-9 -8 -7 -6 -5 -4 3 -2 -1
Рисунок 16. Индексация в строке, списке и кортеже
Чтобы получить элемент по его индексу, нужно указать имя переменной, содержащей коллекцию, и нужный индекс в квадратных скобках:
коллекция[индекс]
Так мы можем получить отдельные символы строки и элементы списка и кортежа:
mpssage_str = Как дела?"
print(message_str[Jj)
#	Вывод: К
print(message_str[3])
#	Вывод: (Пробел тоже является отдельным символом)
shoppinglist = [ 'Еда 1 "Еда 2", "Еда 3", "Еда 4", "Еда 5"]
print(shopping-list[4])
#	Вывод: Еда 5
print(shopping-list[-3])
#	Вывоа: Еда 3
friends_tuple = ("Друг 1', "Друг 2", "Друг 3 )
print(friends_tjple[u])
#	Вывод: Друг 1
print(friends_tuple[-3])
#	Вывод: Друг 1
Попытка обратиться к элементу по несуществующему индексу приведет к исключению IndexError:
+riends_tuple = ('Друг Г"Друг 2", "Друг 3 )
print(friends_tuple[9])
#	Ошибка: IndexError: tupLe index out of range
Коллекции 142
Использование срезов
Для извлечения не одного элемента, а сразу целого подмножества элементов строки, списка и кортежа в Python предусмотрен удобный механизм срезов Срез позволяет получить часть коллекции в соответствии с заданными параметрами:
коллекция [start:stop:step]
Параметрами среза любой коллекции являются;
о start - начальный индекс среза (включительно), по умолчанию start=0 о stop - конечный (не включительно) индекс среза.
о step - шаг извлечения элементов, по умолчанию step=l.
Срезы, каки функция range(), используют параметры
start, stop и step, и одинаково задают границы и шаг получения элементов. Однако срезы применяются непосредственно к строкам, спискам и кортежам, позволяя выбирать их подмножества, тогда как range () создает отдельную последовательность целых чисел. При этом создаваемая последовательность чисел соответствует индексам, выбираемым в срезе. То есть, задавая функции range() границы и шаг, вы получаете именно те индексы, которые будут использованы при формировании среза с такими же параметрами.
Срез коллекции представлен тем же типом данным, что и сама коллекция: срезом строки будет строка (называемая подстрокой), срезом списка - список, а срезом кортежа - кортеж.
Ни один из параметров среза не является обязательным, поэтому без учёта шага возможны следующие конструкции получения среза коллекции:
о [: ] - выбирает всю коллекцию целиком:
message_str = "Как дела?" message_str_copy = message_str[:] pci nt(message_str_copy) # Вывод: Как дела?
shoppinglist = ['Еда 1", ‘Еда 2 , "Еда 3', "Еда 4", "Еда 5"]
Коллекции 143
shopping_list_cору = shopping_list[:] print(shoppi ng_list_copy)
#	Вывод: ["Еда Г, "Еда 2", "Еда 3", "Fda 4“t "Еда 5"]
friends_tuple = ("Друг 1", ‘Друг 2 , 'Друг 3")
friends_tuple_copy = friends_tuple[:] print(friends_tuple_copy)
#	Вывод: ("Друг 1“, "Друг 2", "Друг 3")
о [start: ] - выбирает элементы от индекса start до конца коллекции.
message_str = "Как дела?" print(message_srr[4:]) # Вывод: дела?
shopping,list = ["Еда 1", "Еда 2", "Еда 3", "Еда 4 , Еда 5']
print(shopping_list[2:])
#	Вывод: ['Еда З‘л 'Еда 4’, 'Еда 5']
friends_tuple = ("Друг 1", "Друг 2 , "Друг 3") print(friends_tuple[l:])
#	вывод: ('Друг 2'г ‘Друг 3')
о [: stop] - выбирает элементы от начала коллекции до индекса stop (не включая его):
message_str = "Как дела?" print(messagp_str[: 3]) # Вывод: Как
shopping_list = ["Еда 1", "Еда 2", "Еда 3", "Еда 4", "Еда 5"]
print(shopping_list[: 2])
#	Вывод: ['Еда 1', 'Еда 2']
+riends_tuple = ("Друг 1", Друг 2"} "Друг 3")
print(friends_tup]e[:2])
#	вывод: ('Друг 1'Л 'Друг 2')
о [start: stop] - выбирает все элементы с индекса start по индекс stop (не включая его): message_str = "Как дела?" print(message_str[0:3]) # Вывод: Как
shoppinglist = ["Еда 1", "Еда 2", "Еда 3", "Еда 4 , "Еда 5"] print(shopping_list[2:4])
#	Вывод: ['Еда 3'Еда 4']
friends_tuple = ("Друг 1", 'Друг 2", "Друг 3")
print(friends_tuple[1:3])
#	Вывод: ('Друг 2'} 'Друг 3')
Коллекции 144
Кроме того, в срезах разрешается использовать отрицательные индексы и даже совмещать их с положительными:
message_str = "Как дела?"
print(message_str[-9:-6])
#	Вывод:Как
sbopping_list = ["Еда 1 "Еда 2 , "Еда 3", "Еда 4", "Еда 5"] print(shopping_list[-4:5])
#	Вывод: ['Еда 2‘, 'Еда 3‘, ‘Еда 4‘, ‘Еда 5']
friends_tuple = ( 'Друг 1 , "Друг 2", “Друг 3 )
print(friends_tuple[-2:])
#	Вывод: ('Друг 2‘, 'Друг 3')
Шаг и (влечения элементов
Параметр step всегда указывается после второго двоеточия и определяет шаг извлечения элементов из коллекции. По умолчанию step=l, то есть элементы извлекаются последовательно слева направо. Но если указать step=2. то будет извлечён каждый второй элемент:
message_str = 'Как дела?"
print(message_str[::2])
#	Вывод: Ккдл?
shopping_list = [ 'Еда 1", "Еда 2 , "Еда 3", "Еда 4", "Еда 5"]
print(sbopping_list[::2])
#	Вывод: ['Еда 1'Еда 3', 'Еда 5']
friends_tupie = ( 'Друг 1", "Друг 2”, "Друг 3 )
print(friends_tuple[::2])
#	Вывод: ('Друг 1‘, ‘Друг 3’)
Здесь мы не указывали параметры start и stop, поэтому в срез была извлечена вся коллекция.
Для получения элементов с нечетными индексами можно начать срез с индекса 1:
message_str = 'Как дела?" print(message str[1:: 2]) # Вывод: а еа
shopping_list = ['Еда 1 , "Еда 2 , "Еда 3', "Еда 4", "Еда 5"]
print(shopping_list[l::2J)
#	Вывод: ['Еда 2', 'Еда 4‘]
friends_tuple = ('Друг 1", 'Друг 2’, "Друг 3 )
print(friends_tupJe[l::2])
#	Вывод: (‘Друг 2',)
Коллекции 145
Использование отрицательного шага, например step=-l, позволяет получить коллекцию в образном порядке: mess<?ge_str = ''как дела?" print(message_str[::-1]) # Вывод: 'алед как
shopping_list = ['Еда 1", "Еда 2 , ’Еда 3", "Еда 4", "Еда 5"] print(shupping_list[::-1J)
#	Вывод: [‘Еда 5‘, 'Еда 4', 'Еда 3', 'Еда 2', 'Еда 1']
friends_tuple = (Друг 1 , "Друг 2", "Друг 3 ) print(friends_tuple[::-!])
#	Вывод: ('Друг 3’, 'Друг 2', 'Друг 1')
Индексация во вложенных коллекциях
Коллекции могут содержать другие коллекции, создавая вложенные структуры. Например, рассмотрим кортеж, содержащий два списка: cities = (
["Москва , 'Санкт-Петербург"!, ["Череповец”, "Вологда"]
)
print(cities[l][0])
#	Вывод: Череповец
Для доступа к элементам вложенных списков используется двойная индексация. Первый индекс 1 обращается ко второму элементу кортежа: списку ["Череповец", "Вологда"], а второй индекс 0 - к первому элементу этого вложенного списка: строке "Череповец".
Уровень вложенности может быть любым, так что возможно использование трёх индексов и более.
Также из вложенных коллекций можно извлекать срезы: cities = (
["Москва ', "Санкт-Петербург"j,
["Череповец", ["Вологда", "Шексна1 , "Устюжна ]] ) print(cities[1][1][:2]) # Вывод: ['Вологда'j 'Шексна']
Рассмотрим элементы, получаемые с помощью такого индекса:
о cities[l] - возвращает второй элемент кортежа cities: список ["Череповец", ["Вологда", "Шексна", "Устюжна"]].
о cities[ 1] [1] - возвращает второй элемент первого вложенного списка: список ["Вологда", "Шексна", "Устюжна"].
Коллекции 146
о cities[ 1] [ 1 ][:2] - возвращает срез второго вложенного списка: список ["Вологда", "Шексна"]
Примеры
Пример 1. Обработка лоюк
Логи сервера хранятся в формате «[2025-01-25 14:30:45] ERROR: Connection timeout». Для их обработки программа с помощью срезов выделяет дату и событие в отдельные переменные:
log_entry = "[2025-01-25 14:30:45] ERROR: Connection timeout"
#	Извлечение даты и времени
timestamp = log_entry[l:20] # Срез символов с 1 по 19
event = Jog_entry[22:] Я Всё после 22 символа
print (-Г'Дата: {timestamp} )
print(f"Событие: {event} )
Вывод:
Дата: 2025-01-25 14:30:45
Событие: ERROR: Connection timeout
Пример 2. Анализ среднесуточной температуры в июне
При анализе среднесуточной температуры июне для экспериментов требуется только часть общего объёма данных, которая копируется с помощью среза:
#	Данные по среднесуточной температуре в июне
temperatures = [18.5, 23.1, 22.3, 25.7, 21.9, 25.6, 27.4, 21.2, 21.3, 20.9, 35.7, 21.8, 24.1, 29.6, 28.9, 20 5, 26.8, 25.3, 27 1, 23.2, 24.4, 22.6, 21.7, 23.8, 20.5, 20.3, 21.9, 26.4, 21.2, 21.5]
#	Скопируем небольшой участок данных для экспериментов
sample_data = temperatures[:10]
#	Изменим последний элемент образца данных sample_data[-1] += 10
print('Образец модифицированных данных: , sample_data) print('Образец исходных данных: , temperatures[:10])
Вывод:
Образец модифицированных данных: [18.5, 23.1, 22.3, 25.7, 21.9, 25.6, 27.4, 21.2, 21.3, 30.9]
Образец исходных данных: [18.5, 23.1, 22.3, 25.7, 21.9, 25.6, 27.4, 21.2, 21.3, 20.9]
Коллекции 147
Пример 3. Проверка палиндромов
Онлайн-сервис предоставляет возможно проверить, является ли введённая строка палиндромом. То есть строкой, читающейся одинаково слева направо и справа налево (например, «заказ», «шалаш»):
s = input( «ведите слови; ")
reverse_s = s[::-l] # Сорока в обратном порядке
if s == reverse_s:
print( Строка является палиндромом")
else:
print('Строка не является палиндромом')
Вывод:
Введите слово: мадам
Строка является палиндромом
И i o* и
J Индекс - это порядковый помер элемента в упорядоченной коллекции (строке, списке и кортеже). С помощью индекса можно получить символ строки или элемент списка или кортежа.
J Положительные индексы начинаются с 0, отсчитывая элементы с начала коллекции, а отрицательные индексы начинаются с -1, отсчитывая элементы с конца.
S Конструкция [start:stop:step] позволяет получить срез коллекции в соответствии с заданными параметрами: start задаёт начальный индекс среза (включительно), stop - конечный индекс (не включительно), a step -шаг извлечения элементов. Все параметры необязательные.
✓ Индексы и срезы применимы ко вложенным коллекциям. Уровень вложенности не ограничен.
Задания для самопроверки
1.	Дан список numbers = [10, 20, 30, 40, 50]. Как получить третий элемент списка? Используйте как положительный, так и отрицательный индекс.
2.	Дан кортеж fruits = (["яблоко", "Банан"], ["Апельсин", "Киви"]). Используйте индексы и выведите на экран строку "Банан".
3.	Дан список colors = ["Красный", "Оранжевый", "Жёлтый", "Зелёный", "Голубой", "Синий", "Фиолетовый"]. Какие срезы получатся, если использовать
следующие конструкции:
Коллекции 148
colors[2:]
colors[l:4]
colors[-5:2]
colors[l::2]
colors[-2:-6:-1]
4.	Дана строка sentence = "Весна, весна на улице, весенние деньки!". Выведите на экран строку, состоящую из каждого третьего символа этой строки.
5.	Измените порядок символов в строке user = "радар", записав её задом наперёд с помощью среза.
3.	3 ПЕРЕБОР ЭЛЕМЕНТОВ КОЛЛЕКЦИЙ
Все встроенные коллекции в Python, такие как списки, кортежи, строки, множества и словари, объединяет то, что они являются итерируемыми объектами (англ, iterable object), то есть такими объектами, которые поддерживают возможность последовательного прохода по своим элементам. Это означает, что мы можем перебрать каждый элемент таких коллекций в цикле for:
for элемент in коллекция:
действия_с_элементом
Основное преимущество итерируемых объектов заключается в том, что цикл for берет на себя работу по перебору элементов. Вместо того чтобы вручную обращаться к каждому элементу, мы можем последовательно перебрать каждый элемент такого объекта.
Давайте посмотрим на простой пример.
fruits = [ Яблоко", "Банан'1, "Апельсин"] for fruit in fruits: print(fruit)
#	Вывод: Яблоко
#	Вывод: Банан
#	Вывод: Апельсин
Здесь цикл for проходит по каждому элементу списка fruits. На каждой итерации переменная fruit принимает значение очередного элемента списка.
Таким способом мы можем можно перебирать элементы любой встроенной коллекции:
movie = "Тор" for char in movie: print(char)
#	Вывод: T
#	Вывод: о
Коллекции 149
ft Вывод: p
Перебор элементов словаря
По умолчанию цикл for перебирает ключи словаря: student_data = { Имя": "Михаил , "Фамилия": ’Романн, , "Возраст": 19} for data in student_data:
prirt(data)
#	Вывод: 'Лмя
ft Вывод: Фамилия
#	Вывод: Возраст
Последовательность значений словаря возвращает метод diet .values().
Метод	diet. values()
Описание	Возвращает последовательность значений словаря diet
Возвращаемое
Последовательность значении словаря значение
Такую последовательность можно перебрать в цикле for: student_data = {"Имя": "Михаил", "Фамилия": "Романов", "Возраст": 19} for data in student_data.values(): print(data) # Вывод: Михаил if Вывод: Романов ff Вывод: 19
Также существует специальный метод diet.keys(), который явно возвращает последовательность ключей словаря
Метод	diet.keys()
Описание	Возвращает последовательность ключей словаря diet
Возвращаемое значение	Последовательность ключей словаря
Результат перебора такой последовательности будет совпадать с результатом перебора самого словаря:
student_data = {"Имя": "Михаил", "Фамилия": "Романов", "Возраст": 19}
for data in student_data.keys():
Коллекции 150
print(data)
#	Вывод: Имя if Вывод: Фамилия it Вывод: Возраст
Однако наиболее распространенный способ перебора элементов словаря -использование метода diet .items (), который возвращает последовательность пар (ключ, значение).
Метод	diet, items ()
Описание
Возвращаемое значение
Возвращает последовательность пар (ключ, значение) словаря diet
Последовательность пар (ключ, значение) словаря
В таком случае после ключевого слова for объявляется не одна, а две переменные: для ключей и для значений:
stats = {'Здоровье": 162, "Сила атаки': 34}
for key, value in stats.items(): print(f"{key} {value} )
#	Вывод: Здоровье - 162
i	f Вывод: Сила атаки - 34
Здесь переменная key последовательно принимает все значения ключей словаря, a value - значений.
Перебор элементов по индексу в цикле for
Строки, списки и кортежи - это упорядоченные коллекции, то есть у каждого элемента есть свой порядковый номер (индекс), по которому можно обратиться к этому элементу. При этом индексы образуют последовательность целых чисел от 0 до индекса последнего элемента коллекции.
Перебор индексов в цикле for
Последовательность индексов любой коллекции можно создать с помощью функции range() и перебрать в цикле for. Чтобы узнать, сколько всего элементов хранится в любой встроенной коллекции, мы можем воспользоваться функцией 1еп() (от англ, length - длина).
Коллекции 151
len(iterable)
Функция
Описание	Возвращает количество элементов в коллекции iterable
Параметры	• iterable - итерируемый объект, количество элементов которого требуется определить
Возвращаемое значение	Целое число
Если в списке, например, 5 элементов, то его индексы представляю) собой последовательность чисел от 0 до 4. Такую последовательность индексов от 0 до 1еп() - 1 удобно сгенерировать через функцию range() и перебрать в цикле for: heroes = [ Чип", "Дейл", "Гаечка ] for i in range(len(heroes)): print(heroes[ij) it Вывод: Чип it Вывод: Дейл # Вывод: Гаечка
Здесь переменная i последовательно принимает значение от 0 до 2, поэтому на каждой итерации цикла мы обращаемся к соответствующему элементу коллекции heroes.
Перебор пар (индекс, значение) в цикле for
Однако для получения индекса необязательно использовать конструкцию range(len()), так как существует специальная функция enumerate(), возвращающая кортеж из номера итерации цикла (по умолчанию совпадает с индексом), и текущего значения.
Функция	enumerate(sequence, start=0)
Описание	Возвращает кортеж (счётчик, значение) для каждого элемента коллекции sequence * sequence - коллекция (итерируемый объект)
Параметры	Необязательные параметры: • start - значение, с которого начинается отсчёт. По умолчанию start=0
Возвращаемое значение	Итератор кортежей (счётчик, значение)
Коллекции 152
Все встроенные коллекции являются итерируемыми объектами, то есть их можно последовательно перебрать в цикле for. Объект, который обеспечивает этот перебор, называется итератором.
Цикл for неявно превращает итерируемый объект в итератор и затем запрашивает у него элементы один за другим. Поэтому итератор кортежей также сразу можно перебирать в цикле for Однако в таком случае следует объявить не одну, а две переменные: для индекса и для значения:
planets = ("Кибертрон’,, "Татуин", "Солярис") for index, value in enumerate(p]anets): print(f"Планета {value} находится по индексу {index} )
#	Вывод: Планета Кибертрон находится по индексу 0
#	Вывод: Планета Татуин находится по индексу 1
#	Вывод: Планета Солярис находится по индексу 2
Для того, чтобы начать отсчёт не с 0, мы можем присвоить параметру start какое-то значение, например, 100:
celestials = [ Аришем', "Эсон", "Незарр"]
fon num, value in enumerate(celestials, 100): print(f"Целестиал {value} под номером {num}')
#	Вывод: Целестиал Аришем под номером IBB
#	Вывод: Целестиал Эсон под номером 162
#	Вывод: Целестиал Незарр под номером 102
Перебор элементов по индексу в цикле while
Перебирать элементы упорядоченных коллекций по индексу можно не только в цикле for, но и в цикле while:
robins = ["Дик Грейсон", "Тим Дрейк", "Джейсон Тсдц"] i = 0 while i < len(robins):
print(f"Jly4mnft Робин  {robins[i]}") i += 1
#	Вывод: Лучший Робин - Дик Грейсон
#	Вывод: Лучший Робин - Тим Дрейк
#	Вывод: Лучший Робин - Джейсон Тодд
В таком случае мы вручную управляем индексом 1, увеличивая его на каждой итерации, пока не достигнем конца коллекции.
Примеры
Пример 1. Анализ продаж по категориям
Протрамма рассчитывает общую выручку по категориям товаров из
Коллекции 153
данных за день:
sales = { "Электроника": 150000, ‘'Одежда": 80000, "Книги": 35000
}
total = 0
for category, amount in sales.items(): print(f {category}: {amount} руб. ) total += amount
print(Г'==================\пОбщая выручка: {total} руб.")
Вывод:
Электроника: 150000 руб.
Одежда: 80000 руб.
Книги: 35000 руб.
Общая выручка: 265000 руб.
Пример 2. Поиск самой низкой температуры
Программа находит наименьшее значение в списке со среднесуточными температурами за неделю:
temperatures = [15, 12, 18, 23, 9, 16]
min_temperature = temperatures[0] for each_temperature in temperatures: if each_temperature < min_temperature: min temperature = each_temperature
print(f‘Наименьшая температура: {minjtemperature}')
Вывод:
Наименьшая температура: 9
Пример 3. Отслеживание выполнения задач по номеру
Программа выводит на экран каждую задачу из списка с её порядковым номером:
tasks = ["Написать отчет", "Отправить письма", "Запланировать встречу"]
print( Список задач )
#	Нумерация начнется с 1, так как start-1
	for index, task in enumerate(tasks, start=l): print(f'{index) -{task} )
Коллекции 154
Вывод:
Список задач:
1.	Написать отчет
2.	Отправить письма
3.	Запланировать встречу
11'101 И
Все встроенные коллекции в Python являются итерируемыми объектами, то есть такими объектами, которые поддерживает возможность последовательного прохода по своим элементам.
Итератор - это объект, обеспечивающий последовательный доступ к элементам итерируемого объекта. Цикл for неявно превращает итерируемый объект в итератор и затем запрашивает у него элементы один за другим.
J Цикл for - это основной инструмент для перебора элементов коллекций.
J Метод diet.values() возвращает последовательность значений словаря, J Метод dict.keys() возвращает последовательность ключей словаря.
J Метод diet, items() возвращает последовательность пар (ключ, значение).
J Упорядоченные коллекции (строки, списки и кортежи) можно перебирать по индексу.
J Функция 1еп() возвращает количество элементов в коллекции.
Конструкция range(1еп(коллекция)) создаёт последовательность индексов коллекции, которую можно перебрать в цикле for.
S Функция enumerate() возвращает итератор кортежей (счётчик, значение).
J Упорядоченные коллекции можно по индексу перебрать в цикл while, вручную управляя счётчиком индекса.
Задания для самопроверки
1.	Дано множество unique_numbers = {5, 2, 8, 2, 9, 5}. Выведите на экран все элементы этого множества. Обратите внимание на то. что дубликаты будут выведены только один раз.
2.	Дан словарь city_population = {"Москва": 13149863, "Санкт-Петербург": 5597763, "Владивосток": 622782}. Для каждого города выведите на экран строку в формате "{Город} - население {количество}".
3.	Запросите у пользователя целое число п > 1 и выведите на экран все чётные числа от 2 до п включительно на одной строке через пробел.
Коллекции 155
Пример входных данных
8
17
12
Пример выходных данных
2 4 6 8
2 4 6 8 10 12 14 16
2 4 6 8 10 12
4.	Запросите у пользователя строку и выведите на экран каждой символ этой строки на одной строке через пробел. Используйте цикл while.
Пример входных данных Пример выходных данных Мышь	М
Провод	П
Ток	Т
ш ь
О L- О Д
К
Ы
Р
О
3.	4 ОБЩИЕ ФУНКЦИИ И ОПЕРАТОРЫ КОЛЛЕКЦИЙ
Несмотря на то. что коллекции в Python имеют свои уникальные особенности. они обладают некоторыми общими способами работы с ними. Так мы можем получить количество элементов любой коллекции и проверить принадлежность элемента ей. а также определить максимальное и минимальное значение или посчитать сумму коллекции чисел.
Размер коллекции
Мы уже встречались с функцией 1еп(), когда говорили о переборе элементов упорядоченных коллекций по индексу. Но она позволяет узнать, сколько элементов содержится в любой встроенной коллекции Python
Например, определим количество элементов в множестве:
weapons = ("Меч , "Лук", ’Копье")
print(len(weapons))
# Вывод: 3
Или количество пар «ключ-значение» в словаре:
characters = { Аладдин": 18, "Жасмин": 15, "Джафар": 60} print(ten(characters)) # Вывод: 3
Максимальный и минимальный элемент коллекции
Функции max() и min() определяют, соответственно, наименьший и
Коллекции 156
наибольший элемент в коллекции.
Функции	max(iterable, default=He_yKaaaHO, key=None)
Описание	Возвращает элемент с максимальным значением в коллекции iterable • iterable - итерируемый объект, среди элементов которого ищется наибольшее значение Необязательные параметры:
Параметры	•	default - значение, которое будет возвращено, если итерируемый объект iterable окажется пустым, по умолчанию не указано и вызывается исключение ValueError •	key функция, применяемая к каждому элементу итерируемого объекта перед поиском максимального значения, по умолчанию key^None
Возвращаемое значение	Наибольшее значение
Функция	min(iterable, default=He указано, key=None)
Описание	Возвращает элемент с минимальным значением в коллекции iterable • iterable - итерируемый объект, среди элементов которого ищется наименьшее значение Необязательные параметры:
Параметры	•	default - значение, которое будет возвращено, если итерируемый объект iterable окажется пустым, по умолчанию не указано и вызывается исключение ValueError •	key - функция, применяемая к каждому элементу и гери-руемого объекта перед поиском минимального значения, по умолчанию key=None
Возвращаемое значение	Наименьшее значение
Когда элементы коллекции представлены числами, минимальное и максимальное значение определяются арифметически:
numbers = (21, 23, 100, 24, 25)
Коллекции 157
print(max(numbers))
#	Вывод: J0tJ
print(min(numbers)) # Вывод- 21
Строки сравниваются лексикографически, то есть символ за символом, основываясь на порядковых номерах символах в Юникоде. При этом заглавные буквы меньше строчных:
tongue_twister = ( Ехал , "Грека , "через', "реку ) print(max(tongue_twister)) # Вывод: через print(min(tongue_twisrer)) # Вывод: Грека
Параметр key позволяет указать функцию, которая будет применяться к каждому элементу коллекции перед сравнением их значений. Например, мы можем найти не просто наибольшую строку, а строку с наибольшей длиной:
poems = [ ’Ямб", "Хорей", "Дактиль']
print(max(poems)) # Лексикографическое сравнение # Вывод: Ямб
print(max(poems, key=len)) # Сравнение no длине строки
#	Вывод: Дактиль
Параметр default не указан по умолчанию, и если передать функциям шах() или min() пустую коллекцию, то будет вызвано исключение ValueError: empty_lst = [] print(max(empty_lst)) # Ошибка: VaLueError. max() iterabLe argument is empty
Однако с указанным параметром default эти функции вернут переданное значение:
empty_lst = []
print(max(empty_lst, default=None)) # Вывод: None
Поиск максимального и минимального элемента может быть проведён не только в коллекции, но и среди объектов, перечисленных через запятую: print(max(l, -1, 10, -10)) # Вывод: 10 print(min(10, 11, 12, -10а)) # Вывод: -100
Следует учитывать, что функции тах() и min() ожидают, что все элементы поддерживают сравнение между собой. Попытка сравнить, например, число со строкой или строку со списком приведет к исключению ТуреЕггог:
Коллекции 158
some_values = [ Это строка”, 1, "Снова строка’, 2] print(max(some_va]ues))
#	Ошибка: ТуреЕггог: ’>' not supported between instances of 'int' and 'str'
Проверка истинности элементов коллекции
Функции апу() и а11() очень полезны, когда нужно проверить истинность или ложность элементов коллекции. Эти функции неявно применяют функцию bool() к каждому элементу коллекции для того, чтобы получить его логическое значение - True или False.
Так, если хотя бы один элемент в коллекции является истинным (True), то функция any() тоже возвращает True, и возвращает False
Функция а11(), наоборот, проверяет истинность всех элементов коллекции и возвращает True, если все элементы истинны (True). Если хотя бы один элемент ложный (False), то она тоже возвращает False.
Функция	any(iterable)
Описание	Возвращает True, если хотя бы один элемент в коллекции iterable истинен, иначе - False
Параметры	• iterable - итерируемый объект, истинность элементов которого проверяется
Возвращаемое значение	True или False
Функция	all(iterable)
Описание	Возвращает True, если все элементы в коллекции iterable истинны, иначе - False
Параметры	• iterable - итерируемый объект, истинность элементов которого проверяется
Возвращаемое значение	True или False
Вспомним, чго ложными в Python считаются все пустые объекты, ноль любого типа и обьект None, а все непустые объекты и ненулевые числа будут истинными:
Коллекции 159
1st = [“Хлеб", "Молоко", "Конфеты"] print(any(lst)) # Вывод: True print(all(lst)) # Вывод. FaLse
numbers = (0, 0.0, None) print(any(numbers)) # Вывод: FaLse print(all(numbers)) # Вывод: FaLse
money = { '100 рублей1 , 1000, 10, 5, "1 юань"} print(any(money)) # Вывод: True
print(all(money)) # Вывод: True
Работа функции any() похожа на последовательное применение оператора or (логическое «или») между логическими значениями элементов коллекции: print(any([False, False, True])) # Вывод: True print(False or False or True) # Вывод: True
print(any([False, False, False]))
# Вывод: FaLse print(False or False or False) # Вывод: FaLse
А работа функции all() похожа на последовательное применение оператора and (логическое «и»): print(all([True, True, True])) # Вывод: True print(True ana True ana True) # Вывод: True
print(all([True, True, False])) if Вывод: FaLse
print(True and True and False) if Вывод: FaLse
Сумма элементов коллекции
Если коллекция состоит из чисел, то функция sum() поможет быстро вычислить их сумму.
Коллекции 160
sum(iterable start=0)
Функция
Описание	Возвращает сумму всех элементов в коллекции iterable • iterable - итерируемый объект, сумма элементов которого вычисляется
Параметры	Необязательные параметры: • start - начальное значение суммы, по умолчанию start=0
Возвращаемое значение	Число
Например, посчитаем сумму кортежа или значений словаря: numbers_tuple = (1и, 20, 30) print(sum(numbers_tuple)) # Вывод: 60
numbersdict = {1: 100, 2: 200, 3: 300} print(sum(numbers_dict.values())) # Вывод: 60в
Однако попытка сложить элементы, которые не являются числами (например, строки), приведет к ошибке ТуреЕггог:
str_numbers = 4)123456789" print(sum(str_nuiibers))
#	Ошибка: ТуреЕггог: unsupported operand type(s) for +: 'int' and 'str'
Проверка принадлежности элемент а коллекции
Мы уже рассматривали работу операторов сравнения и идентичности, а также логических операторов, которые возвращают True или False в зависимости от условия,
Кроме них в Python существует оператор принадлежности in (с англ, в). Он возвращает True, если элемент присутствует в коллекции, и False в противном случае: элемент in коллекция
Например, можно проверить содержит ли строка символ или другую строку:
print( с" in "Сладкая сирень' )
#	Вывод: True
Коллекции 161
print('сирень’ in "Сладкая сирень ) # Вывод: True
Или содержит список или множество какой-либо элемент:
artifacts = ['Рог изобилия", "Бездонный графин", 'Волшебная лампа"] print( Бездонный графин in artifacts) # Вывод: True
witchers = {'Геральт’, "Весемир", "Эскель’) if Зесемир in witchers:
print( Весемир доблестно сражался!") # Вывод: Весемир доблестно сражался!
В словаре можно проверить наличие элемента как среди ключей, так и среди значений: actors = {
"Генри Кавилл": "Геральт', "Аня Чалотра": 'Иеннифэр1', "Фрейя Аллан": "Цирилла" } print( Генри Кавилл" in actors) it Ищем среди ключей it Вывод: True print( Геральт" in actors.values()) # Ищем среди значений # Вывод: True
Л также проверить наличие пары (ключ, значение) получив их последовательность с помощью метода dict.items():
actors = {
"Генри Кавилл": "Геральт1 , "Аня Чалотра": "Иеннифэр”, "Фрейя Аллан": ’Цирилла" } print(('TeHpn Кавилл", "Супермен ) in actors.items()) # Вывод: False
Оператор in может совмещаться с логическим отрицанием (not):
элемент not in коллекция
Тогда проверяется отсутствие элемента в коллекции:
birds = ['Синица', "Снегирь', "Воробей’] print( Голубь" not in birds) # Вывод: True
Распаковка коллекций
Давайте вспомним операцию множественного присваивания, которое позволяет присваивать значения сразу нескольким переменным в одной строке:
Коллекции 162
year, age = 2008, 16
На самом деле множественное присваивание реализуется через кортежи, и здесь происходит распаковка кортежа (2008, 16) в переменные year и age (при создании кортежа обязательна запятая, а не скобки).
Таким образом можно распаковывать не только кортежи, но и другие коллекции:
coordinates = [10, 20, 30]
х, у, z = coordinates
prlnt(f"x = {х}, у = {у}, z = {z}')
#	Вывод: х = 10, у = 20, z = 30
Здесь список coordinates распаковывается в переменные х, у и г в том же порядке, в котором элементы расположены в списке.
Коллекции можно распаковывать не только в переменные, но и, например, для передачи их элементов в функции или для создания новых коллекций. Для этого предназначен оператор * (звёздочка). Поэтому мы можем вывести на экран не просто список элементов, а каждый его элемент, как отдельный объект:
meals = [ ’Борщ", "Плов', "Пюре"]
print(*meals)
#	Вывод: Борщ Плов Пюре
print(meals)
#	Вывод: ['Борщ', 'Плов', 'Пюре']
Или даже создать новый список, просто распаковав другие списки в квадратных скобках:
soups = ["Борщ", "Щи1, "Грибной суп"]
salads = [ Оливье", "Крабовый салат"]
menul = [*soups, *salads]
print(menul)
#	Вывод: ['Борщ', 'Щи', 'Грибной суп', 'Оливье', 'Крабовый салат']
menu2 = [soups, salads]
print(menu2)
#	Вывоа: [['Борщ', 'Щи', ‘Грибной суп'], ['Оливье', 'Крабовый салат']]
Для словаря, представляющего собой набор пар «ключ-значение», оператор * позволяет получить последовательность объектов, являющихся ключами:
menu = ("Борщ": 75, "Куриный суп": 52, 'Грибной суп": 67} print(*menu, sep=" | )
#	Вывод: Борщ I Куриный суп / Грибной суп
Это может быть удобно для вывода их на экран или для создания новою списка па основе ключей словаря Однако таким образом нельзя создать новый словарь Для получения как ключей, так и значений словаря, следует
Коллекции 163
использовать оператор ** (две звёздочки):
soupjnenu = {'Бирщ": 75, "Куриным суп": 52, "Грибной суп": 67}
desserts_menu = {'Эклер": 56, "Шоколадный пончик": 95} menu = {**soup_menu, **desserts_menu} print(menu)
#	Вывод: {'Борщ': 75, 'Куриный суп': 52, 'Грибной суп': 67, 'Эклер': 56, 'Шоколадный пончик': 95}
Но оператор ** распаковывает словарь именно как пары «ключ-значение», поэтому их нельзя вывести на экран, одиако это позволяет создать новый словарь из нескольких других, то есть объедини гь их.
Упаковка коллекций
Оператор * позволяет не только распаковывать коллекции, но и упаковывать их. Если при множественном присваивании каждому элементу распаковываемой коллекции не соответствует своя переменная (или нам не нужны некоторые значения из этой коллекции), то оператор * может упаковать оставшиеся элементы в список:
first, *middle, last = (1, 2, 3, 4, 5) print(ffirst = {first}, middle = {middle}, last = {last}') # Вывод: first = 1, middLe = [2, 3, 4], Last = 5
Оператор * можно использовать только один раз, иначе интерпретатор не поймёт, как распределить элементы. Если же элементов в коллекции недостаточно для создания списка, то создаётся пустой список:
name, *education = ("Матвей,)
print(f'HMn: {name), образование: {education}’)
#	Вывод: Имя: Матвей, образование: []
Также если какие-то элементы коллекции не нужны, то их можно проигнорировать с помощью подчеркивания (_):
result = ( град", "дождь', “снег , "молоко")
weatherl, *weather2, _ = result
print(f"Сначала был {weatherl}, а потом - {weather2} )
#	Вывод: Сначала был град, а потом - ['дождь', 'снег']
Здесь пропущен последний элемент списка, так как на его месте вместо переменной для распаковки указан символ _. Так принято обозначать переменные, которые не будут использоваться.
Коллекции 164
Примеры
Пример 1. Проверка полей формы на заполненность
Форма регистрации на сайте предлагает пользователю заполнить обязательные поля: имя пользователя, электронная почта и пароль, и дополнительные поля возраст и номер телефона:
form_data = {
"username": "great_wizard", "email":
"password": "qwertyl23",
’age":	,
"phone_number": ""
}
#	Создаем списки для проверки
required_values = [form_data[ 'username"], form_data["email"], form_data[ 'password11 ]]
optional_values = [form_data['age'], form_data.get( phone' )]
#	Проверяем заполнение обязательных полей
all_filled = all(required_values)
print( Спасибо за регистрацию!" if all_filled else "He все обязательные поля заполнены!')
#	Проверяем заполнение дополнительных полей
any_filled = any(optional_values)
print("Заполнено хотя бы одно дополнительное поле:", any_filled)
Здесь для вывода сообщения об успешной регистрации используется тернарный оператор, который выводит сообщение с благодарностью за регистрацию только если заполнены все обязательные поля формы.
Вывод:
Не все обязательные поля заполнены!
Заполнено хотя бы одно дополнительное поле: False
Пример 2. Анализ температурных данных
Программа определяет максимальное, минимальное и среднее значение
температуры за неделю:
temperatures = [22.5, 18.3, 25.1, 19.8, 23.4, 27.9, 21.2]
hottest = max(temperatures) # Максимальная температура
coldest = min(temperatures) # Минимальная температура
average = sum(temperatures) / len(temperatures) # Средняя температура
Коллекции 165
print(f"Максимальная температура: {hottestpc") print(f Минимальная температура: {<oldest}°C") print(f Средняя температура: (average:.If}°C")
Вывод:
Максимальная температура: 27.9°C
Минимальная температура: 18.3°С
Средняя температура: 22.6°С
Пример 3. Поиск русских букв в строке
Программа считает количество русских букв в строке, которую ввёл пользователь.
russian_ietters = "абвгдеёжзийклмнопрсгуфхцчшщъыьэюя" russian_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя" text = input( 'Введите строку для подсчёта русских букв )
russian_letters_count • 0 for char in text:
if char.lower() in russian_letters: russian_letters_count += 1 print(f"Количество русских букв. {russian_letters_count} )
Вывод:
Введите строку для подсчёта русских букв: Python - это просто awesome Количество русских букв: 9
Htoim
J Оператор принадлежности in в конструкции элемент in коллекция возвращает True, если элемент присутствует в коллекции, иначе - False.
J Распаковка - это извлечение элементов из итерируемого объекта (например, списка, кортежа, множества), а упаковка - это объединение элементов в один объект.
J Распакованные элементы могут быть присвоены другим переменным, переданы функции или использоваться для создания новой коллекции. Оператор * используется для распаковки и упаковки любых коллекций.
J Оператор ** используется для распаковки словарей.
Коллекции 166
Таблица 11 - Общие функции коллекций
Функция	Описание
len(iterable)	Возвращает количество элементов в коллекции iterable
any(iterable)	Возвращает True, если хотя бы один элемент в коллекции iterable истинен, иначе - False
all(iterable)	Возвращает True, если все элементы в коллекции iterable истинны, иначе - False
max(iterable, de-f aulб=не_указано, key-None)) min(iterable, de-fault=He_yказано, key=-None)) sum(iterable start=0)	Возвращает элемент с максимальным значением в итерируемом объекте iterable Возвращает элемент с минимальным значением в коллекции iterable Возвращает сумму всех элементов в коллекции iterable
Задания для самопроверки
1.	Дан список numbers = [15, 8, 23, 8, 42, 10]. Вычислите и выведите на экран размер этого списка и сумму всех элементов списка.
2.	Дан список grades = [3, 4, 5, 3, 4, None, 5, 5, None]. Проверьте все ли студенты получили какую-то оценку (не None). Результат выведите на экран.
3.	Дан кортеж fruits = ("Яблоко", "Банан", "апельсин", "киви"). Найдите и выведите на экран лексикографически наибольшее и наименьшее слово в этом кортеже.
4.	Дан словарь prices = {"Авокадо": 200, "киви": 150, "Голубика": 175}. Проверьте есть ли в словаре пара с ключом "Авокадо" и значением 125. Результат выведите на экран.
5.	Объясните своими словами, что такое распаковка и упаковка коллекций? Чем отличается распаковка словаря от распаковки списка?
6.	Запросите у пользователя число п > 1 и после этого запросите на ввод п строк, которые сохраните список. Если этому списку принадлежит строка "Яблоко", то npoi рамма выводит строку "Вы любите яблоки", иначе - "Вы не любите яблоки"
Коллекции 167
Пример входных данных
2
Яблоко
Г руша
4
Банан
Ананас
Апельсин
Г руша
1
Яблоко
Пример выходных данных
Вы любите яблоки
Вы не любите яблоки
Вы любите яблоки
3.5 МЕТОДЫ ПРЕОБРАЗОВАНИЯ И ВЫРАВНИВАНИЯ СТРОК
Протраммируя на Python, мы уже сталкивались как с функциями, так и с методами. Функции не привязаны к объектам и вызываются независимо от них. Например, функция id() возвращает уникальный идентификатор объекта, а функция print () выводит данные на экран.
Однако методы, напротив, принадлежат определённым типам данным (классам) и используются только совместно с объектом, к которому относятся. Например, уже рассмотренный метод diet .values() возвращает последовательность значений словаря diet.
Строки имеют большое количество методов, позволяющих совершать разные действия над ними, например, привести все символы строки к верхнему регистру или обрезать пробелы в начале и конце строки.
Но строки являются неизменяемым типом данных, поэтому методы, преобразующие строку, на самом деле не изменяют её, а возвращают изменённую копию исходной строки, которую следует сразу использовать в работе или присвоить какой-либо переменной.
Изменение регистра букв строки
Прежде всего, вспомним, что буквы могут находиться в двух регистрах: о нижний ре« истр — маленькие или строчные буквы ("ведомо во"); о верхний pel иор - заглавные или прописные буквы (‘'КГБ").
Коллекции 168
В большинстве текстов совмещаются буквы обоих регистров, как например. в предложении, которое вы сейчас читаете.
Приведение строки к единому регистру
Для преобразования всех букв строки в нижний регистр предназначен метод str.lower() а для приведения к верхнему регистру - метод str.upper().
Метод	str.lower()
Описание	Приводит все буквы строки str к нижнему регистру
Возвращаемое значение	Преобразованная копия строки
Метод	str.upper()
Описание	Приводит все буквы строки str к верхнему регистру
Возвращаемое значение	Преобразованная копия строки
Приведение строк к единому регистру часто бывает полезным при сравнении текстовых данных, особенно введенных пользователем. Например, пользователь может ввести слово "привет" различными способами, например, "Привет", "привет" или "Привет". Приводя все варианты к одному регистру, мы можем избежать множественных проверок:
messagpl = input()
if messagel.lower() == "привет’:
print( И тебе привет, дорогой друг!' )
#	Ввод: ПрИвЕт
#	Вывод: И тебе привет, доросой друг!
message? = input()
if message?.upper() == "ПОКА": print( И тебе всего хорошего!') # Ввод: покА
#	Вывод: И тебе всего хорошего!
Изменение регистра отдельных частей строки
Помимо полного приведения к верхнему или нижнему регистру, существуют методы для изменения регистра отдельных частей строки.
Коллекции 169
Метод str.capitalize() преобразует первую букву строки в верхний регистр. а все остальные - в нижний, а метод str.title() приводит к верхнему регистру первую букву каждого слова в строке, а остальные буквы делает строчными.
Метод
str.capitalize()
Описание
Приводит первую букву строки str к верхнему регистру, а все остальные - к нижнему
Возвращаемое значение
Преобразованная копия строки
Метод
str.title()
Описание
Приводит первую букву каждого слова строки str к верхнему регистру, а остальные - к нижнему
Возвращаемое значение
Преобразованная копия строки
Метод str .capitalize() часто используется для форматирования предложений, имен собственных или заголовков, где необходимо выделить начало строки: name = иван" print(name.сapitalize()) # Вывод: Иван
article = ‘Самая ВКУСНАЯ оКрОшКа всего за 30 МИНУТ" print(article.capitalize())
#	Вывод: Самая вкусная окрошка всего за Зв минут
И если метод str.capitalize() приводит строку к виду предложения, то метод str.title() основан на правилах написания заголовков в английском языке, где каждое значимое слово начинается с заглавной буквы:
book = "Learn PYTHON the HARD WAY" print(book.title())
#	Вывод: Learn Python The Hard Way
Слова в строке определяются как последовательности букв, разделенные любыми небуквенными символами, в том числе апострофами:
message = "What's up, bro?"
Коллекции 176
print(message.title())
#	Вывод: What'S Up, Bro?
Но если строка состоит из одного слова, то результаты методов str.ti-tle() и str.capitalize() совпадут:
city = черепОВЕЦ"
p^intCcity.capitalize())
#	Вывод: Череповец
print(city.title())
it Вывод: Череповец
У даление символов в начале и в конце с i роки
При копировании данных из разных источников, а также при вводе данных пользователем в начале и конце строки мотут содержать пробелы и другие лишние символы. Если они мешают дальнейшей обработке строки, то их можно убрать с помощью метода str. strip().
Метод
str.strip(chars=" ")
Описание
Параметры
Возвращаемое значение
Удаляет в начале и конце строки str символы chars
Необязательные параметры:
• chars - строка символов, которые нужно удалить, по умолчанию chars=" " (пробел)
Преобразованная копия строки
Но умолчанию метод удаляет пробелы, а также символы табуляции и перевода строки:
name = " Жуков Георгий Константинович!!!\t\t\n
print( Приветствую, , name.strip())
#	Вывод: Приветствую, Жуков Георгий Константинович!!!
Но параметр chars позволяет явно указать символы для удаления:
name = " zzz!Кутузов, Михаил Илларионович!zzz
print( Приветствую,", name.strip( '!z ))
#	Вывод: Приветствую, Кутузов, Михаил Илларионович!
Порядок символов в строке chars не имеет значения, удаляются все вхождения любого из этих символов в начале и конце строки
Коллекции 171
Если требуется удалить символы только в начале или только в конце строки, то у метода str.strip() есть варианты с префиксами «1» (от англ, left -лево) и «г» (от англ, right - право), которые удаляют символы слева или справа соответственно. Такое использование префиксов характерно для многих методов в Python.
Метод	str.lstrip(chars=" ")
Описание	Удаляет в начале строки str символы chars Необязательные параметры:
Параметры	• chars - строка символов, которые нужно удалить, по умолчанию chars=" " (пробел)
Возвращаемое значение	Преобразованная копия строки
Метод	str.rstrip(chars=" ")
Описание	Удаляет в конце с i роки str символы chars Необязательные параметры:
Параметры	• chars - строка символов, которые нужно удалить, по умолчанию chars=" " (пробел)
Возвращаемое значение	Преобразованная копия строки
Например, мы уверены, что пользователь может ввести лишние пробелы только в начале строки: name = ' Райковский Викентий Логгинович" print(name.Istri р()) # Вывод: Райковский Викентий Логгинович
Или при копировании данных из файла лишние символы подчеркивания могут появиться только в конце строки: occupation = "Генерал________"
print(occupation.rstrip( # Вывод: Генерал
Коллекции 172
Замена символов в строке
Если нужно не просто обрезать символы в начале и конце строки, а заменить одни символы на другие, то для этого предназначен метод str.replace().
Метод
Описание
Параметры
Возвращаемое значение
str.replace(old, new, count=-l)
Заменяет все (или count) вхождения подстроки old на подстроку new в строке str
•	old - подстрока, которую необходимо заменить
•	new - новая подстрока, которая заменит old
Необязательные параметры:
•	count - максимальное количество замен, по умолчанию count=-l, что означает замену всех вхождений
Преобразованная копия строки
В самом простом случае методу str. replace() достаточно передать заменяемое и новое значение:
poetic_line = "Ласточка с весною в сени к нам летит." new_poetic_line = poetic_line.replace("Ласточка , "Голубка") pri nt (new_poetic_li ne)
#	Вывод: Голубка с весною в сени к нам летит.
Но с помощью параметра count можно О1раничить количество замен:
poetic_line = "С нею солнце краше. И весна милей..."
new_poetic_line = poetic_line. replace("c , "S", 3) print(new_poetic_line)
#	Вывод: С нею Золнце краше. И веЗна милей....
Если подстрока для замены не найдена, метод str.replace() возвращает исходную строку без изменений:
poetic_l.ine = "Прощебечь с доргги нам привет скорей!" rtew_poetic_line = poetic_line.replace( дам тебе я зерен ', "песню спой ) print(new_poetic_line)
#	Вывод: Прощебечь с дороги нам привет скорей!
Разбиение строки на список
Метод str.split() преобразует строку в список подстрок, разделяя её по
Коллекции 173
пробелу или указанному разделителю.
Метод	stг.split(sep=" ", maxsplit=-l)
Описание	Разделяет строку str на список подстрок, используя в ка-
честве разделителя строку sep или пробел
Необязательные параметры:
•	sep - строка-разделитель, по умолчанию sep=" " (про-Парамезры	бел)
•	maxsplit - максимальное количество разбиений, по умолчанию maxsplit=-l, то есть без ограничений
Возвращаемое ,,
С писок строк значение
Например, у нас есть строка с жанрами фильмов, и с помощью метода str. split() мы можем разбить её на список, используя занятую в качестве разделителя-
genres = "комедия,фантастика,романтика"
genres_list = genres.split(”,' )
print(genres_list)
#	Вывод: ['комедия'; 'фантастика', 'романтика']
Если значение разделителя sep не указано, то строка разбивается по пробельным символам (пробел, табуляция, перенос строки) как по одному, так и по нескольким подряд:
musicals = "Кабаре Отверженные Эвита” print(musicals.split())
#	Вывод: ['Кабаре', ‘Отверженные', ‘Эвита']
Параметр maxsplit позволяет ограничить количество разбиений. Тогда будет произведено не более maxsplit разбиений, а остаток строки будет добавлен в список как последний элемент:
message = "Он купил хлеб, молоко, колбасу и конфеты"
1st = message.split(",", 1)
print(lst)
#	Вывод: ['Он купил хлеб', ' молоко, колбасу и конфеты']
Здесь строка разбивается на две части только по первой запятой.
Коллекции 174
str.join(iterable)
Объединяет элементы итерируемого объекта iterable в строку, разделяя их строкой str
• iterable - итерируемый объект, элементы которого объединяются в строку
Строка
Объединение элементов коллекции в строку
В то время как метод str.split() разделяет строку на список подстрок, метод str.join(). наоборот, объединяет все элементы коллекции (которые должны быть строками) в одну новую строку.
Метод
Описание
Параметры
Возвращаемое значение
Этот метод берёт каждую строку из коллекции и вставляет строку-разделитель между ними, тем самым создавая новую строку:
sentence = ("Сегодня", "идёт", "снег ] _ __	II II
sep = _
sentence_str = sep.join(sentence)
print(sentence_str)
#	Вывод: Сегодня_идёт_снег
При этом, если хотя один элемент объединяемой коллекции не является строкой, то вызывается исключение ТуреЕггог:
grades = [ Отлично", 'Хорошо , 3J — _ _	II II
Sep =
grades_str = sep.join(grades)
#	Ошибка: ТуреЕггог: sequence item 2: expected str instance, int found
Выравнивание строк
Методы выравнивания строк добавляют определенные символы-заполнители в начало пли конец строки до тех пор, пока строка не достигнет заданной длины
По умолчанию строка уже выравнена по левому краю, однако это также можно сделать явно с помощью метода str.ljust() (от англ, left justify - выравнивать слева) который добавляет символы-заполнители в конец строки.
Коллекции 175
Метод	str.1just(width, fillchar=H ")
Описание	Выравнивает строку str но левому краю до длины width с помощью добавления символа fillchar в конец строки • width - желаемая длина строки
Параметры	Необязательные параметры: • fillchar - символ-заполнитель, по умолчанию fill-char=" "(пробел)
Возвращаемое значение	Преобразованная копия строки
По правому краю строку выравнивает метод str.rjust() (от англ, right justify - выравнивать справа), добавляющий символы-заполнители в начало строки
Методы str.ljust() и str.rjust() отличаются префиксами «1» и «г»,обозначающими по какому краю выравнивается строка.
Метод	str.гjust(width, fillchar=" ") Выравнивает строку str по правому краю до длины
Описание	width с помощью добавления символа fillchar в начало строки • width - желаемая длина строки
Параметры	Необязательные параметры: • fillchar - символ-заполнитель, по умолчанию f ill -char=" " (пробел)
Возвращаемое значение	Преобразованная копия строки
По центру строку выравнивает метод str.center(), который добавляет символы-заполнители в начало и конец строки.
Коллекции 176
Метод	str.center(width, fillchar=" ") Выравнивает строку str ио центру до длины width с по-
Описание	мощью добавления символа fillchar в начало и конец строки • width - желаемая длина строки
Параметры	Необязательные параметры: • fillchar - символ-заполнитель, по умолчанию f ill -char=" " (пробел)
Возвращаемое значение	Преобразованная копия строки
Используя методы выравнивания строк, создадим простую таблицу с данными об играх:
о С голбец "Название" сделаем шириной 20 символов и выровняем по левому краю.
о Столбец "Год выхода" ограничим до ширины 15 символов и выровняем по центру.
о Столбец "Жанр" сделаем шириной 20 символов и выровняем по правому краю.
#	Создаем заголовки
title = "Название".ljust(20, )
year = "Год выхода'.center(15, "-') genre = "Жамр*.rjust(20,	)
#	Создаем ячейки первой строки gameltitle = 'Марио .ljust(20, ) gamel_year = "1985м.center(15, "-") gamelgenre = "Платформер".rjust(20,	)
#	Построчно выводим на экран print(f {title)|{year}|{genre}") print(f {gamel_title}|{gamel_year}|{gamel_genre} )
Тогда на экран будет выведена следующая таблица:
Название-------------|—Год выхода--|-------------------Жанр
Марио---------------- -1-----1985-----1----------Платформер
Так, используя только строки и методы их выравнивания, мы создали простую и наглядную таблицу.
Коллекции 177
Также метод str. гjust() полезен для выравнивания чисел путём добавления ведущих нулей:
print( 23“.rjust(5, "О"))
#	Вывод: 00023
Однако для этой цели существует более специализированный метод -str. zfi 11 (width) (от англ, zero fill - заполнить нулями), который дополняет строку ведущими нулями до указанной длины.
Метод	str.zfill(width)
Описание	Выравнивает строку str по правому краю до длины width с помощью добавления нулей в начало строки
Параметры	• width - желаемая длина строки
Возвращаемое значение	Преобразованная копия строки
Добавление ведущих нулей используется для приведения чисел к единому формату. Например, если данные представляют собой шестизначные числа, но некоторые числа имеют меньше цифр, то дополнение их нулями делает информацию более удобной для восприятия:
print( -791".zfill(6))
#	Вывод: -000791
Примеры
Пример 1. Нормализация пользовательского ввода команд
Пользователь может вводить команды в разных регистрах или с лишними пробелами. Программа должна обрабатывать команду «выход» независимо от того, как её написал пользователь:
command = input("Введите команду. )
normalized-command = command.strip().lower() if normalized_command == выход":
print(f"Завершение работы')
else:
print(f Команда '{command} не распознана')
Вывод:
введите команду:	ВыХОД
Завершение работы
Коллекции 178
Пример 2. Создание отчёта о наличии зоваров на складе
Программа выводит на экран отчёт о наличии товаров на складе с колонками фиксированной ширины:
#	Исходные данные sales = [
("Ноутбук", 15, 45000),
("Монитор1, 8, 1200г),
("Клавиатура", 23, 1500) ]
#	Создаём заголовки
item_title = "Товар .ljust(15)
count_title = "Кол-во".center(10)
price_title = "Цена".гjust(10)
print(item_title + count_title + price_title) print(	* 35)
#	Заполняем таблицу
	For sale in sales:
item = sale[0].ljust(15)
count = str(sale[l]).zfill(3).center(10)
price = f (sale[2]}".rjust(10)
print(item + count + price)
Вывод:
Товар	Кол-во	Цена
Ноутбук	015	45000
Монитор	008	12000
Клавиатура	023	1500
Пример 3. Генератор лоз инов из ФИО
Протрамма создаёт логин сотрудника в формате «фамилия инициалы» на основе полного имени:
full_name = "Ковалевская Софья Васильевна"
#	Разделяем ФИО на части parts = full_narne. split ()
#	Формируем логин: фамилия + первая буква имени + отчества login = f"{parts[0]}_{parts[i ] [И] }{parts[2] [0]}". lower() print(f Логин сотрудника: {login} )
Вывод:
Логин сотрудника: ковалевскаясв
Коллекции 179
Итоги
Таблица 12 - Метод str.lower()	Методы преобразования и выравнивания строк Описание Приводит все буквы строки str к нижнему регистру
str.upper() str.capitalize() str.title() str.strip(chars=" ") str.lstrip(chars=" ") str.rstrip(chars=" ") str.replace(old, new, count=-l) str.split(sep="	", maxsplit=-l) str.join(iterable) str.center(width, fillchar=" ") str.ljust(width, fill-char^ ") str.rjust(width, fill-char=" ") str.zfill(width)	Приводит все буквы строки str к верхнему регистру Приводит первою букву строки str к верхнему регистру, а все остальные - к нижнему Приводит первую букву каждого слова строки str к верхнему регистру, а остальные - к нижнему Удаляет в начале и конце строки str символы chars или пробелы Удаляет в начале строки str символы chars или пробелы Удаляет в конце строки str символы chars или пробелы Заменяет все вхождения подстроки old на подстроку new в строке str Разделяет строку str на список подстрок, используя в качестве разделителя строку sep Объединяет элементы итерируемого объекта iterable в строку, разделяя их строкой str Выравнивает строку str но центру до длины width с помощью добавления символа fillchar в начало и конец строки Выравнивает строку str по левому краю до длины width с помощью добавления символа fillchar в конец строки Выравнивает строку str по правому краю до длины width с помощью добавления символа fillchar в начало строки Выравнивает строку str по правому краю до длины width с помощью добавления нулей в начало строки
Коллекции 180
Задания для самопроверки
1.	Почему методы преобразования строк в Python возвращают новую строку, а не изменяют исходную?
2.	Запросите у пользователя строку s и символ sep для разбиения этой строки на список Разбейте строку s на список по разделителю sep и выведите
этот список на экран.
Пример входных данных мука,молоко,яйца
Пример выходных данных ['мука', 'молоко', 'яйца']
яблони_и_груши
и
рыба+или*курица
*
['яблони', 'груши']
['рыба', 'или', 'курица']
3.	Выведите на экран строку "Привет, друг!", выровненную по центру до длины 20 символов символом
4.	Как удалить лишние пробелы и символ "№" в начале и конце строки Заказ №298 NW"? Каким бы методом вы воспользовались для удаления пробелов только в начале строки?
5.	Запросите у пользователя строку. Если пользователь написал "да" в любом регистре, то выведите на экран строку "Продолжение работы", а если "нет", то - "Завершение работы" Если пользователь ввёл строку, отличную от ответа "да" или "нет", то выведите на экран строку "Команда не распознана".
Пример входных данных Пример выходных данных
дА	Продолжение работы
НЕТ	Завершение работы
Не зНаЮ	Команда не распознана
3.6 МЕТОДЫ ПОИСКА И ПРОВЕРКИ СТРОК
При работе со строками довольно часто возникает необходимость найти нужное сочетание символов внутри строки или подсчитать, сколько раз оно встречается в этой строке. Также часто требуется убедиться в том, что строка соответствует нужному формату, например, состоит только из чисел или начинается с определённой строки.
Коллекции 181
Поиск подстроки в строке
Для поиска подстроки в строке Python предоставляет два основных метода: str. index() и str.find(). Оба метода предназначены для обнаружения перво) о вхождения указанной подстроки и возвращают индекс начала этого вхождения
Ключевое различие между этими двумя методами заключается в их поведении. когда подстрока не найдена:
о метод str.index() в такой ситуации вызовет исключение ValueError, что приведёт к прерыванию выполнения программы;
о метод st г. find () просто вернёт -1, и программа дальше продолжит работу.
Методы	str.index(sub, start=0, end=None) str.find(sub, start-0, end-None)
Описание	Возвращают начальный индекс первого вхождения подстроки sub в строку str • sub - искомая подстрока I {еобязательные параметры:
Параметры	•	start - индекс начала среза, в котором ищется подстрока sub. По умолчанию starts© •	end - индекс конца среза, в котором ищется подстрока sub. По умолчанию end=None (до конца строки)
Возвращаемое значение	Целое число
По умолчанию поиск подстроки осуществляется по всей строке:
proverb = "Век живи - Еек учись" print(proverb.index( Зек")) # Вывод: в
citate = ' Один за всех, все за одного ' print(citate.find( за")) # Вывод: 5
Но оба метода поддерживают ограничение диапазона поиска с помощью среза [start:end]:
proverb = век живи - • ек учись"
Коллекции 182
print(proverb.index( Век”, 6, 14)) # Вывод: 11
citate = "Один за всех, все за одного"
рci nt(citate.fi nd("за1, 15, 21))
#	Вывод: 18
В случае отсутствия искомой подстроки в строке метод str. index() вызовет исключение ValueError, а метод str.find() просто вернёт -1.
proverb = "Век живи - Век учись"
print (proverb. iridex("бездельничай1 ))
#	Ошибка: VaLueError: substring not found
citate = "Один за всех, все за одного"
print(citate.find( д'Артаньян' ))
#	Вывод: -1
Также у обоих методов есть варианты с префиксом «г», которые начинают поиск не с начала строки, а с конца (справа), поэтому возвращают индекс последнего вхождения подстроки.
Методы	str.rindex(sub, start=0, end=None) str.rfind(sub, start=0, end=None)
Описание	Возвращают начальный индекс последнего вхождения подстроки sub в строку str • sub - искомая подстрока Необязательные параметры:
Парамеция	* start - индекс начала среза, в котором ищется подстрока sub. По умолчанию start=0 • end - индекс конца среза, в котором ищется подстрока sub. По умолчанию end=None (до конца строки)
Во твращаемое значение	Целое число
Так. если методы str.index() и str.find() возвращают наименьший индекс начала подстроки, то методы str.rindex() и str.rfind() - наибольший: proverb = “Семь раз отмерь, один раз стрежь print(proverb.index(”pa3 )) # Вывод: 5 print(proverb.rindex( 'раз')) it Вывод: 22
Коллекции 183
citate = "Казань брал, Астрахань брал, Ревель брал. Шпака - не брал" print(citate.find("6pan )) # Вывод: 7 print(citate.rfind("брал )) # Вывод: 53
В случае отсутствия искомой подстроки в строке метод str.rindex() аналогично вызывает исключение ValueError, а метод str.rfind() возвращает -1;
proverb = "Семь раз отмерь, оцин раз отрежь" pri nt (proverb, г index( восемь ))
#	Оыивка. VaLueError. substring not found
citate = "Казань брал, Астрахань брал, Ревель брал, Шпака - не брал" print(citate.rfind("захватил )) # Вывод: -1
Подсчёт количества вхождений подстроки в строку
Когда необходимо не просто найти индекс начала вхождения подстроки, а посчитать общее количество её вхождений, используется метод st:r.count().
Метод	str.count(sub, start=0, end=None)
Описание	Возвращает количество вхождений подстроки sub в строку str • sub подсчитываемая подстрока Необязательные параметры:
Параметры	•	start - индекс начала среза, в котором ищется подстрока sub. По умолчанию start=0 •	end - индекс конца среза, в котором ищется подстрока sub. По умолчанию end=None (до конца строки)
Возвращаемое значение	Целое число
По умолчанию подсчёт вхождений осуществляется ио всей строке:
citate = Неважно, сколько раз вы падали; важно, сколько раз вы поднимались" print(citate.count("cKOflbKO ))
# Вывод: 2
Но как и поиск, область подсчёта можно ограничить помощью среза [start:end]:
Коллекции 184
citate = "Неважно, сколько раз вы падали; важно, сколько раз вы поднимались print(citate.count( ’сколько", 2, 18)) # Вывод: 1
Проверка регистра символов строки
Метод str.islower() проверяет регистр всех букв в строке на принадлежность к нижнему регистру, а метод str.isupper() - к верхнему. Как и большинство методов (и функций), которые что-либо проверяют (возвращают True или False), они начинаются со слова is (с англ. - быть, являться).
Метод	str.islower()
Описание	Возвращает True, если все буквы в строке str маленькие, иначе - False
Возвращаемое значение	True или False
Метод	str.isupper()
Описание	Возвращает True, если все буквы в строке str заглавные, иначе - False
Возвращаемое значение	True или False
Если все буквы строки находятся в проверяемом регистре, то оба метода возвращают True
username = input('Введите имя пользователя: ") if username.islower():
print( хорошее маленькое имя пользователя')
elif username.isupper():
print("ХОРОШЕЕ БОЛЬШОЕ ИМЯ ПОЛЬЗОВАТЕЛЯ")
# ввод: Введите имя пользователя: повелитель_кексиков
# Вывод: хорошее маленькое имя пользователя
Но если строка состоит одновременно из заглавных и маленьких букв, то оба метода вернут False:
message = 'Что делаешь?" print(message.isiower()) # Вывод: FaLse
print (message. isupperQ)
Коллекции 185
# Вывод FaLse
Проверка типа символов строки
Для проверки того, являются ли все символы строки только буквами используется метод str. isalpha(), а только цифрами - метод str. isdigit().
Мет од	str.isalpha()
Описание	Возвращает True, если строка str состоит из букв, иначе -False
Возвращаемое значение	True или False
Метод	str.isdigit()
Описание	Возвращает True, если строка str состоит из цифр, иначе - False
Возвращаемое значение	True или False
Например, введённое пользователем имя не должно содержать никаких символов, кроме букв, поэтому его можно проверить с помощью метода str.isalpha():
name = input( 'Введите имя: ) if not name.isalpna():
print( Имя может содержать только буквы! ’) else:
print С Какое хорошее имя!")
# Ввод Введите имя: Е102-гамма
# Вывод: Имя может содержать только буквы!
Возраст же может представлять собой только число, поэтому для проверки корректности его ввода используем метод str.isdigitQ:
age = irput( Bi едите возраст: ") if not age.isdigit():
print(‘ьозраст может содержать только цифры! ) else:
print( Какой чудесный возраст!")
#	Ввод Введите возраст: 100
#	Вывод: Какой чудесный возраст!
Коллекции 186
Но если строка может включать в себя как буквы, так и из цифры, то проверить это можно с помощью метода str.isalnum().
Метод
str.isalnumO
Описание
Возвращает True, если строка str состоит из букв или цифр, иначе - False
Возвращаемое значение
True или False
Это метод не гребует одновременного наличия букв и цифр в строке и вернёт True как в случае, если строка состоит только из букв, так и если она состоит только из цифр:
password = input( введите пароль: )
if not password.isalnum():
print('Пароль может содержать только буквы или цифры!") else:
print( Это самый лучший пароль на свете1 )
#	Ввод: Введите пароль: qwerty123
#	Вывод: Это самый лучший пароль на свете!
Все методы проверки типа символов в строке возвращают False, если строка содержит пробелы или другие не буквенно-цифровые символы. Однако метод str. is space () позволяет проверить строку на го, состоит ли она только из одних пробелов.
Метод	str.isspace()
Описание
Возвращает True, если строка str состоит из пробелов, иначе - False
Возвращаемое значение
True или False
Данный метод вернёт True, в гом числе, если строка содержит символы табуляции или перевод на новую строку: username = \t \n print(username.isspace()) # Вывод: True
Коллекции 187
Проверка начала и конца строки
Также можно проверить строку на соответствие определённому шаблону. Так. метод str .startswith() проверяет начинается ли строка с указанной подстроки (префикса), а метод str.endswith(). наоборот, проверяет заканчивается ли она указанной подстрокой (суффиксом).
Mei од	str.startswith(prefix, start-0, end-None)
Описание	Возвращает True, если строка str начинается на подстроку prefix, иначе - False • prefix - строка-префикс, которой предполагаемо начинается строка Необязательные парамо ры:
Параметры	• start - индекс начала среза, в котором проверяется наличие строки-префикса. По умолчанию start-0 * end - индекс конца среза, в котором проверяется наличие строки-префикса. По умолчанию end=None (до конца строки)
Возвращаемое значение	True или False
Метод	str.endswith(suffix, start=0, end=Nane)
Описание	Возвращает True, если строка str оканчивается на подстроку suffix, иначе - False • suffix - строка-суффикс, которой предполагаемо заканчивается строка Необязательные параметры:
Параметры	•	start - индекс начала среза, в котором проверяется наличие строки-постфикса. По умолчанию start=0 •	end - индекс конца среза, в котором проверяется наличие строки-суффикса. По умолчанию end=None (до конца строки)
Возвращаемое значение	True или False
Коллекции 188
Например, так можно проверить фамилию по ФИО:
name = input('Введите ФИО ) if name.startswith("Романов ):
print( Да это же Романовы!")
elif name.startswith( ’Рюрикович’'): print( Да это же Рюрико!-ичи!") # Ввод: Введите ФИО: Романова Е.А. # Вывод: Ди это же Романовы!
Или проверить расширение файла.
filename = input('Введите название файла: ) if filename.endswith( .ру’):
print( Это файл с кодом на Руthen' )
elif filename.endswith( .docx'):
print( Это файл с документом MS Word )
# Ввод: Введите название файла: tasb.py # Вывод: Это файл с кодом на Python
Как и другие методы поиска подстроки, методы str.startswith() и str.endswith() поддерживают ограничение области поиска начала и конца строки с помощью среза [start:end]:
article = “Самая лучшая шинксака для капусты" print(article.startswith("Самая", 6)) # Вывод: FaLse print(article.endswith("капусты", 5, 15)) # Вывод: FaLse
Примеры
Пример 1. Поиск ключевого слова в отзыве
Программа проверяет, содержит ли отзыв клиента упоминание о доставке и где именно оно находится:
review = "Очень вкусная пицца, но хотелось бы более быструю доставку..."
#	Так как слов "доставка" может изменяться по падежам, то ищем слово "до-ставк"
delivery_index = review.find('доставк')
if aelivery_index 1= -1:
print(f"Слово ’доставка’ найденс в <-тзыье по индексу: {delivery_index}") else:
print( Слово 'доставка* не найдено в отзыве')
Вывод:
Слово 'доставка' найдено в отзыве по индексу: 50
Коллекции 189
Пример 2. Анализ текста
Онлайн-сервис подсчитывает количество предложений и символов (без пробела) в тексте:
text = "Обаятельный молодой человек по имени Джимми Тесайгер пронесся вниз по большой лестнице особняка Чимниз, перепрыгивая через две ступеньки. Его появление было столь неожиданным, что он столкнулся с Тредуэлл м, величественным дворецким, " тот момент, когда последний пересекал зал, неся новые порции горячего кофе. Никто не пострадал только благодаря изумительному присутствию духа и необычайной ловкости дворецкого."
#	Подсчет количества предложений по знакам препинания в конце предложения sentences = text.count( ) + text.count( "!' ) + text.count("?') print(f”Предложений: {sentences} )
#	Подсчёт количества символов (без прлобела) chars = О
for word in text.split(): # Цикл проходит no каждому слову
for letter in word: # Цикл проходит no каждому символу в слове chars += 1
print(f"Символов (без пробела): {chars}")
Вывод:
Предложений: 3
Символов (без пробела): 360
Пример 3. Проверка корректности адреса электронной почты
При регистрации нового пользователя система проверяет на корректность введённый адрес электронный почты
user_email = input( -.ведите адрес электронной почты: ) is_valid_email = (
user_email.count( ’@") == 1 # Одна @
and not user_email.startswith( jaJ ) # He начинается c @
and not user_email.endswith("@') # He заканчивается на @ )
if is_valid_email:
print(f"Спасибо за регистрацию! Пароль придёт на почту {user_email} ) else:
print( Некорректный адрес электронной почты")
Вывод:
введите адрес электронной почты: cutedragon777@example.ru
Спасибо за регистрацию! Пароль придет на почту cutedragon777@example.ru
Коллекции 190
Итоги
Таблица 13 - Методы поиска и проверки строк
Метод	Описание
str.index(sub, start=0, end=None)	Возвращает начальный индекс первого вхождении подстроки sub в строку str. Если подстрока не найдена, то вызывает исключение ValueError
str.find(sub, start=0, end=None)	Возвращают начальный индекс первого вхождения подстроки sub в строку str. Если подстрока не найдена, то возвращает -1
str.rindex(sub, start=0, end=None)	Возвращает начальный индекс последнего вхождения подстроки sub в строку str. Если подстрока не найдена, то вызывает исключение ValueError
str.rfind(sub, start=0, end=None)	Возвращают начальный индекс последнего вхождения подстроки sub в строку str. Если подстрока не найдена, то возвращает -1
str.count(sub, start=0, end=None)	Возвращает количество вхождений подстроки sub в строку str
str.islower()	Возвращает True, если все буквы в строке str маленькие, иначе - False
str.isupper()	Возвращает True, если все буквы в строке str заглавные, иначе - False
str.isalpha()	Возвращает True, если строка str состоит из букв, иначе - False
str.isdigit()	Возвращает True, если строка str состоит из цифр, иначе - False
str.isalnum()	Возвращает True, если строка str состоит из блкв или цифр, иначе - False
str.isspace()	Возвращает True, если строка str состоит из пробелов, иначе - False
str.starts-with(prefix, start=0, end=None) str.endswith(suffix, start-0, end=None)	Возвращает True, если строка str начинается на подстроку prefix, иначе - False Возвращает True, если строка str оканчивается на подстроку suffix, иначе - False
Коллекции 191
Задания для самопроверки
1.	Найдите первое вхождение слова "мир" в строке "Привет, мир! Как дела, мир?" и выведите его на экран. Поиск должен вестись с индекса 7.
2.	Какие методы используются для поиска последнего вхождения подстроки в строке? Как они ведут себя, если подстрока не найдена?
3.	Используйте метод str. count() и выведите на экран, сколько раз символ "!" встречается в строке "Ура! ! ! Получилось!".
4.	Что вернут методы str.islower() и str.isupper() для строки "Вконтакте"?
5.	Запросите у пользователя строку с почтовыми индексом. Если она состоит только из цифр, то выведите на экран строку "Введён корректный почтовый индекс", иначе — "Почтовый индекс должен состоять только из цифр" Пример входных данных Пример выходных данных 160789	Введен корректный почтовый индекс
34POST	Почтовый индекс должен состоять только из цифр
985172	Введён корректный почтовый индекс
6.	Запросите у пользователя строку с именем файла. Если она заканчивается расширением ".txt", то выведите на экран строку "Это текстовый файл", иначе-"Это не текстовый файл".
Пример входных данных Пример выходных данных
task.txt	Это	текстовый файл
cake.jpeg	Это	не текстовый файл
passwords.txt	Это	текстовый файл
3.7 СПИСКИ И КОРТЕЖИ
Списки и кортежи-это упорядоченные коллекции, которые могут хранить элементы различных типов данных. Их ключевое различие заключается в изменяемости: списки являются изменяемыми объектами, в то время как кортежи неизменяемыми. Это означает, что после создания кортежа вы не можете добавить, удалить или изменить его элементы, в отличие от списка, который поддерживает эти операции.
Списки, благодаря своей гибкости, являются одним из наиболее часто используемых типов данных в Python. Кортежи в свою очередь идеально подходят для хранения набора фиксированных значений, обеспечивая целостность
Коллекции 192
данных, занимая (в некоторых случаях) меньше памяти и демонстрируя более высокую производительность при переборе элементов.
Выбор между списком и кортежем обычно диктуется требованиями конкретной задачи, в частности необходимостью изменения хранимых данных.
Создание списка
Существует несколько способов создания списков в Python.
Квадратные скобки для создания списка
Список создаёт перечисление элементов через запятую внутри квадратных скобок:
emperors = ["П*тр I , "Петр II , "Пётр III"]
Функция list() для создания списка
Любую встроенную коллекцию можно преобразовать в список с помощью функции list ().
Функция	list(iterable)
Описание	Создаёт список из всех объектов коллекции iterable
Параметры
* iterable - итерируемый объект для преобразования в список
Возвращаемое значение
Список
Список, созданный из кортежа, сохранит как сами элементы кортежа, так и их порядок:
famous_places = ('Эрмитаж", "Петропавловская крепость' , "Летнии сад") print(list(famous_places))
#	Вывод: ['Эрмитаж', ‘Петропавловская крепость ', 'Летний сад']
Однако порядок элементов в списке, полученном из множества, непредсказуем, так как элементы множества не упорядочены:
famous_sculptures = { Венера Милосская", "статуя Давида"}
print(list(famous_scu]ptures))
#	Вывод: ['статуя Давида', 'Венера Милосская ']
Элементами строки являются символы, из которых она состоит, поэтому
Коллекции 193
список, созданный из строки, включает в себя все отдельные символы строки: famoiis_poem = "Одиссея'' pri nt(1ist(Famous_poem))
#	Вывод: ['O', 'д', 'и', 'с', 'с', 'е‘, 'я']
При создании списка из словаря по умолчанию используются его ключи: famous_arts = {"Мона Лиза": 1506, "Рождение Венеры": I486} print (list (famous_a rts))
# Вывод: [’Мона Лиза', ‘Рождение Венеры']
Но мы можем получить последовательность значений словаря с помощью метода diet. values() и преобразовать её в список:
famous_arrs = {"Мона Лиза": 1506, 'Рождение Венеры": I486}
print(list(famous_arts.va]ues())) # Вывод: [1506, 1486]
Создание nycioi о списка
Пустой список создают функция list() без аргументов и пустые квадратные скобки:
empen>rs_achievements = list()
emperors_achievements = []
Создание кортежа
Кортежи также можно создавать несколькими способами.
Запятая для создания кортежа
Кортеж создаёт перечисление элементов через запятую внутри круглых скобок или без круглых скобок:
empressl = ("Екатерина I", "Анна I")
empress2 = "Елизавета I , "Екатерина II"
Здесь в обоих случаях создаются кортежи, так как кортеж создаёт именно гапятая. а не скобки. Это используется при множественном присваивании, когда элементы кортежа распаковываются в отдельные переменные:
empress? = "Елизавета I", "Екатерина II" empress_i, empress_ii = empress?
print(empress_i, empress_ii) # Вывод: Екатерина I Екатерина II
Поэтому при создании кортежа из одного элемента, после него нужно обязательно ставить запятую:
Коллекции 194
princess = ("Анна Павловна",)
Без запятой Python интерпретирует выражение как обычное значение типа элемента:
princess = ("Анна Павловна )
print(tyре(princess))
#	Вывод: (class 'str'>
Благодаря тому, что скобки не создают кортеж, мы можем заключать в них длинные строки, и Python интерпретирует это как одну логическую строку.
Функция tuplcQ для создания кортежа
Как и в список, любую встроенную коллекцию можно преобразовать в кортеж с помощью функции tuple().
Функция	tuple(iterable)
Описание	Создаёт кортеж из всех объектов коллекции iterable
Параметры	• iterable - итерируемый объект для преобразования в кортеж
Возвращаемое значение	Кортеж
Тогда, например, из строки будет создан кортеж, состоящий из символов, а из списка, или множества - из элементов:
house = "Палеологи"
print(tuple(house))
#	Вывод: ('П', ‘o', 'л', 'e', 'o', 'л', 'o', 'г', 'и')
children = ["Анна Палеологина' , "Мануил Палеолог ]
print(tuple(children))
#	Вывод: ('Анна Палеологина', 'Мануил Палеолог')
languages = {'Греческий язык', "Латинский язык", 'Итальянский язык"} print(tuple(languages))
#	Вывод: ('Греческий язык', 'Итальянский язык', ‘Латинский язык')
Создание пустого кортежа
Пустой кортеж создают функция tuple() без аргументов и пустые круглые скобки:
empresses_achievenients = tuple()
Коллекции 195
empresses_achievements = ()
Сложение и умножение списков и кортежей
Списки и кортежи поддерживают операции сложения (конкатенации) и умножения (повторения), аналогичные строкам.
Сложение списков и кортежей
Конкатенация списков или кортежей создаёт новый список или кортеж, состоящий из всех элементов объединяемых коллекций:
infantry = ("Стрельцы", "Копейщики", "Гренадеры")
cavalry = ("Рыцари", "Гусары )
warriors = infantry + cavalry
print(warriors)
#	Вывод: ('Стрельцы', 'Копейщики', 'Гренадеры', 'Рыцари', 'Гусары')
Как и при сложении строк и чисел, списки и кортежи допускают использование оператора сложения с присваиванием (+=), который добавляет элементы второй коллекции в конец первой:
history_books = ["Римская история", " Персидский огонь", "От Руси до России"] china_history_boc>KS = ["Китай династии Хань", "Вечный Китай"] history_books += china_history_books print(history_books)
#	Вывод: ['Римская история', ' Персидский огонь', 'От Руси до России', 'Китай династии Хань', 'Вечный Китай']
Кортежи неизменяемы, поэтому оператор += работает как для строк и создаёт новый кортеж, который присваивает переменной слева. Однако списки изменяемы, поэтому оператор += изменяет исходный список, нс создавая новый объект в памяти.
Умножение списков и кортежей
Умножение списка пли кортежа на целое число п похоже на умножение строки на число и создаёт новую коллекцию, в которой исходная последовательность повторяется п раз:
battle_cries = ["За родину!", 'За Сталина!"]
rnore_battle_cries = battle_cries * 3
print(more_battle_cries)
#	Вывод: [‘За родину! ', 'За Сталина! ', ‘За родину!', 'За Сталина!', 'За родину! ', 'За Сталина! ']
Как и конкатенация, данная операция поддерживает оператор умножения
Коллекции 196
с присваиванием (*=):
polite_phrases = ( Хорошего дня!’, "Хороших выходных!")
polite_phrases *= 3
print(polite_phrases)
#	Вывод: (‘Хорошего дня!', ‘Хороших выходных!', 'Хорошего дня!', 'Хороших выходных!', 'Хорошего дня!’, 'Хороших выходных!')
В таком случае будет изменено значение исходного списка или создан объект с новым кортежем, который будет присвоен переменной слева.
Поиск элементов в списках и кортежах
Как и для строк, для поиска индекса первого вхождения искомого элемента в кортеж или список используется метод index().
Методы	list.index(item, start-0, end=None) tuple.index(item, start=0, end=None)
Описание	Возвращают начальный индекс первого вхождения объекта item в список list или кортеж tuple • item - искомый объект Необязательные параметры:
Параметры	• start - индекс начала среза, в котором ищется элемент item. По умолчанию start=0 • end - индекс конца среза, в котором ищется элемент item. По умолчанию end-None (до конца коллекции)
Возвращаемое значение	Целое число
По умолчанию поиск ведётся во всей коллекции:
grades = [4, 4, 5, 5, 4, 5]
print(grades.index(4))
# Вывод: 0
Но также мы можем ограничить область поиска с помощью среза [start:end]:
schedule = ("Русский язык", "Математика", "Математика", "Информатика") print(schedule index( Математика', 2, 4)) # Вывод: 2
Здесь первый найденный элемент имеет индекс 2, так как такой же элемент
Коллекции 197
с индексом 1 исключен из поиска с помощью среза.
Если искомый элемент отсутствует в коллекции, метод вызывает исключение ValueError.
schedule = ["Русский язык", "Математика", "Математика', "Информатика ] print(schedule.index( 'Литература ))
# Ошибка: ValueError: 'Литература ' is not in List
Подсчёт количества вхождений элемента в список или кортеж
Метод count () подсчитывает количество вхождений элемента в список или кортеж.
Методы	list.count(item, start-0, end=None) tuple.count(item, start=0, end-None)
Описание	Возвращают количество вхождений объекта item в список list или кортеж tuple • item - подсчитываемый объект Необязательные параметры:
Параметры	•	start - индекс начала среза, в котором подсчитывается элемент item. По умолчанию start=0 •	end - индекс конца среза, в котором подсчитывается элемент item. По умолчанию end-None (до конца коллекции)
Возвращаемое значение	Целое число
Как и при использовании метода index(). мы можем подсчитывать элемент во всей коллекции или ограничить область подсчёта с помощью среза [start :end]:
breakfast = [ Чай", "Печенье , "Печенье1, 'Печенье", "Варенье"] print(breakfast.count("Печенье")) # Вывод: 3
spoons = (Столовая ложка", "Чайная ложка', "Столовая ложка", "Чайная ложка”) print(spoons.count("Чайная ложка )) # Вывод: 2
Коллекции 198
Примеры
Пример 1. Генератор расписания
Система управления расписанием генерирует шаблон расписания на день на основе повторяющихся событий:
base_schedule = ('Лекция , "Практика”, "Перерыв )
full_day = base_schedule * 3
for i, item in enumeratedull_day, 1): print(f'{!}. {item} )
Вывод:
1.	Лекция
2.	Практика
3.	Перерыв
4.	Лекция
5.	Практика
6.	Перерыв
7.	Лекция
8.	Практика
9.	Перерыв
Пример 2. И {влечение информации о сотруднике
Система получает информацию о сотруднике из базы данных в виде кортежа и присваивает каждый его элемент отдельной переменной для дальнейшей обработки:
user_record = (101, "Лагерта", "Лодброк", "ladgerda.l(a)example.ru")
#	Распаковываем кортеж в отдел иные переменные user_id, first_name, last_name, email = user_record print(f"ir' пользователя: {user_id}") print(f имя: {first_name)") print(f Фамилия: {last_name}‘) print(f Email: {email}")
Вывод:
ID пользователя: 101
Имя: Лагерта
Фамилия: Лодброк
Email: ladgerda.l@example.ru
Пример 3. Отчёт по успеваемости студента
Программа подсчитывает количество разных оценок студента по предмету
grades = [4, 5, 3, 5, 4, 5, 3, 4, 5, 2, 3, 5]
Коллекции 199
#	Цикл проходит по последовательности оценок: 2, 3, 4j 5
for i in range(2, 6):
count_i = grades.count(i) # Подсчёт количества текущей оценки print(f Количество {i} {counti} )
Вывод:
Количество 2: 1
Количество 3: 3
Количество 4: 3
Количество 5: 5
И Ю1 и
✓ Функция list () преобразует другую коллекцию в список.
S Функция tuple() преобразует другую коллекцию в кортеж.
J Кортеж определяется наличием запятой. поэтому не обязательно заключать его элементы в круглые скобки.
Сложение (конкатенация) списков или кортежей создаёт новый список или кортеж. состоящий из всех элементов объединяемых коллекций
s Умножение списка или кортежа на целое число п создаёт новую коллекцию. в которой исходная последовательность повторяется п раз.
✓ Методы list. index() и tuple.index() возвращают начальный индекс первого вхождения искомого объекта в список list или кортеж tuple.
✓ Методы list.count() и tuple.count() возвращают количество вхождений искомого объекта в список list или кортеж tuple.
Задания для самопроверки
1	Чем отличаются списки от кортежей?
2.	Запросите у пользователя строку и выведите на экран список, полученный из символов этой строки.
Пример входных дан- Пример выходных данных ных
Python	['Р', 'у', 't', 'h', 'o', ' n’]
JavaScript	[’3‘, 'a', 'v', 'a', 'S', 'c', 'r', 'i', 'p',
f]
PHP	['P’j’H'j 'P']
3.	Почему при создании кортежа из одного элемента необходимо ставить запятую после него? Что произойдет, если этого не сделать?
Коллекции 200
4.	Дан список words = ["Раз", "Два"]. Используя оператор *, создайте из него список ["Раз", "Два", "Раз", "Два", "Раз", "Два"] и выведите его на экран.
5.	Выведите на экран индекс первого вхождения числа 5 в список [1, 3, 5, 7, 5].
6.	Выведите на экран количество вхождений строки "Физика" в кортеж ("Физика", "Физика", "Математика", "Русский язык", "История").
3.8 МЕТОДЫ СПИСКОВ
Списки в Python обладают множеством встроенных методов, которые значительно упрощают работу с ними. При этом списки изменяемы, поэтому большая часть методов изменяет исходный список и возвращает None Это отличает их от строк, для которых методы возвращают их преобразованную копию.
Добавление элемента в конец списка
Метод list .append() добавляет элемент в конец списка.
Метод
list.append(item)
Описание	Добавляет в конец списка list объект item
Параметры • item объект, который нужно добавить в конец списка
Возвращаемое значение
Например, добавим в конец списка с одеждой новый элемент:
clothes = ["Туника", "Тога"]
clothes.append("Мантия") print(clothes)
#	Вывод: ['Туника', 'Тога', 'Мантия']
При этом если использовать данный метод для добавления другого списка, то он будет добавлен целиком как отдельный элемент:
bats = ["Повязка", ‘Капюшон ] сlothes.append(nats) print(clothes)
#	Вывод: ['Туника', 'Тога', 'Мантия', ['Повязка', 'Капкпион']]
Коллекции 201
Поэтому список hats становится вложенным списком внутри clothes.
Добавление элемента в список по индексу
Элемент можно добавить не только в конец списка, но и по определённому индексу с помощью метода list .insert().
Метод
list.insert(index, item)
Описание
Вставляет объект item в список list но индексу index
Параметры
•	index - индекс, по которому нужно вставить объект item
•	item - объект, который нужно вставить в список
Возвращаемое значение
None
Элемент, находившийся ранее по этому индексу, и все последующие элементы сдвигаются на одну позицию вправо:
meals = [ ’Завтрак", "Ужин"]
meals.insert(1, "Обед")
print(meals)
#	Вывод: ['Завтрак', 'Обед', 'Ужин']
Расширение списка другой коллекцией
Добавить в list.extend():
список все элементы другой коллекции позволяет метод
Метод
list.extend(iterable)
Описание
Расширяет список list всеми элементам итерируемого объекта iterable
Параметры
• iterable - итерируемый объект, элементы которого нужно добавить в конец списка
Возвращаемое значение
None
Результат данного метода совпадает с результатом конкатенации списков, однако метод list.extend() расширяет список элементами не только другого
Коллекции 202
списка, но и любой встроенной коллекции:
accessories = ["Часы", "Шарф J
jewelry = ("Браслет", "Серьги’, "Кольцо")
accessories.extend(jewelry)
print(accessories)
#	Вывод: ['Часы', 'Шарф', 'Браслет', 'Серьги', 'Кольцо']
Если в метод list .extend() передать строку, то она будет посимвольно добавлена в конец списка:
accessories = ["Часы , "Шарф , Браслет", "Серьги", "Кольцо"]
accessories.extend("Боа”)
print(accessories)
#	Вывод: ['Часы', 'Шарф', 'Браслет', 'Серьги', 'Кольцо', 'Б', 'о', 'а']
Удаление элементов списка
Элемент из списка можно удалить как по значению, так и по индексу, а также можно полностью очистить список, удалив все его элементы.
Удаление элемента из списка но значению
По значению элемент из списка удаляет метод list. remove().
Метод	list. remove(item)
Описание	Удаляет из списка list первый найденный объект item
Параметры
Возвращаемое значение
• item - объект, который нужно удалить
None
Если в списке несколько элементов с удаляемым значением, то метод удаляет первый элемент (с наименьшим индексом), в то время как остальные элементы с этим значением останутся в списке без изменений.
gadgets = ["Компьютер", "Телефон", "Планшет", "Компьютер", "Компьютер"]
gadgets.remove("Компьютер1 )
print(gadgets)
# Вывод- ['Телефон', 'Планшет', 'Компьютер', 'Компьютер']
Но если элемент не найден, то будет вызвано исключение ValueError.
Удаление элемента из списка по индексу
По индексу элемент из списка удаляет метод list ,рор(). Кроме этого, он
Коллекции 203
также возвращает этот элемент.
Метод
Описание
Параметры
Возвращаемое значение
list.pop(index=-l)
Удаляет и возвращает из списка list элемент по индексу index
Необязательные параметры:
• index - индекс элемента, который требуется удалить. По умолчанию index=-l, то есть удаляется последний элемент
Удаляемый элемент
Элемент, удаляемый с помощью метода list .рор(), можно сохранить в отдельной переменной и использовать его позже:
ships = ["Фрегат', "Эсминец", "Крейсер", Авианосец"]
deleted_ship = ships.рор(2)
print(f"Удаленный элемент: {deleted_ship}') # Вывод: Удаленный элемент: Крейсер
Не обязательно указывать индекс удаляемого элемента, тогда метод удалит и возвратит последний элемент списка:
desserts = ["Пудинг", "Тирамису", "Чизкейк", "Пастила”] last_deleted_dessert = desserts.рор()
print(f"Удаленный элемент: {Jast_deleted_dessert}') # Вывод: Удаленный элемент: Пастила
Удаление всех элементов списка
Также можно полностью очистить список, используя метод list.clear().
Метод
Описание
Возвращаемое значение
Тогда список станет пустым:
homework = ["Упражнение 225 , ’Параграф 12", "номер 126(6, г)'] homework.clear() print(homework) # Вывод: [j
list.clear()
Удаляет все элементы списка list
None
Коллекции 204
Инверсия элементов списка
Метод list.reverse() инвертирует список, то есть изменяет порядок его элементов на противоположный. Как и другие методы списков, он модифицирует исходный список, делая первый элемент последним, второй - предпоследним и так далее.
Мет од
list.reverse()
Описание	Изменяет порядок элементов списка list на обратный
Возвращаемое
значение
None
После использования этого метода мы не можем вернуться к исходному порядку элементов в списке:
seasons = ["Зина", "Весна", "Лето', "Осень"]
seasons.reverse()
print(seasons)
ft Вывод: ['Осень'j 'Лето', 'Весна', 'Зима']
Если же нужно сохранить исходный список, то следует использовать функцию reversed(). Она не изменяет порядок элементов в исходной коллекции, а создаст обратный итератор этой коллекции.
Функция	reversed(iterable)
Описание	Возвращает обратный итератор итерируемого объекта iterable
Параметры	• iterable - итерируемый объект, который нужно перебрать в обратном порядке
Возвращаемое значение	Обратный итератор
Если итератор - это объект, который обеспечивает перебор элементов, то обратный итератор - это объект, обеспечивающий перебор элементов коллекции в обратном порядке:
years = [2020, 2021, 2022]
reversed_years = reversed(years)
for year in reversed_years:
Коллекции 205
print(year)
#	Вывод: 2022
#	Вывод: 2021
#	Вывод: 2020
Обычно его преобразуют в какую-либо коллекцию для использования её свойств и методов:
years = [1995., 1996, 1997]
reversed_years = list(reversed(years))
reversed_years.append(1998)
print (reversed^ears)
#	Вывод: [1995, 1996, 1997, 1998]
При этом порядок элементов в исходной коллекции останется неизмен
ным.
Сортировка элементов списка
Для того, чтобы отсортировать элементы списка по возрастанию или убыванию, предназначен метод list.sort(). Как и метод list.reverse() он изменяет исходный список.
Метод
Описание
Параметры
list.sort(key=None, reverse=False)
Сортирует элементы списка list по возрастанию или по убыванию
Необязательные параметры:
•	key - функция, применяемая к каждому элементу списка перед сортировкой. По умолчанию key=None
•	reverse - логическое значение, определяющее направление сортировки. По умолчанию reverse=False, что означает сортировку по возрастанию
Возвращаемое значение
None
По умолчанию этот метод сортирует список по возрастанию: temperature = [25, 21, 18, 26, 24, 25] temperature.sort() print(temperature)
#	Вывод: [18, 21, 24, 25, 25, 26]
По убыванию список можно отсортировать, если указать параметр
Коллекции 206
reverse=True'
temperature.sort(reverse=True)
print(temperature)
#	Вывод: [26, 25, 25, 24, 21, 18]
Параметр key позволяет указать функцию или метод, который будет применяться к каждому элементу списка перед сортировкой. При этом функция пли метод в таком случае передаются как объект, а не вызываются, поэтому должны быть написаны без скобок.
Например, по умолчанию список строк сортируется в лексикографическом порядке:
fruits = ["АЬрикос", 'Дыня ', 'Слива , Апельсин"]
fruits.sort() print(fruits) # Вывод: ['Абрикос', 'Апельсин', 'Дыня', 'Слива']
Но мы можем отсортировать его но длине строки, передав параметру key функцию 1еп.
fruits.sort(key=len)
print(fruits)
#	Вывод: ['Дыня', 'Слива', 'Абрикос', 'Апельсин']
Метод li st. sort () изменяет исходный список, поэтому если нужно сохранить порядок элементов и исходном списке и получить его отсортированную копию. то следует использовать функцию sorted().
Функция	sorted(iterable, key=None, reverse=Falsc)
Описание	Создает отсортированный список из элементов итерируемого объекта iterable • iterable - итерируемый объект, который нужно отсортировать Необязательные параметры:
Параметры	•	key - функция, применяемая к каждому элементу списка перед сортировкой. По умолчанию key=None •	reverse - логическое значение, определяющее направление сортировки. По умолчанию reverse=False, что означает сортировку по возрастанию
Возвращаемое значение	Отсортированный список
Коллекции 207
Результат функции sorted() можно присвоить другой переменной, так как она возвращает новый отсортированный список, а не None.
classroom = ["Спортзал", "12А", "Компьютерный класс"] sorted_classroom = sorted(classroom) pri nt(sorted_classroom)
#	Вывод: ['12A', 'Компьютерный класс', 'Спортзал']
При этом в исходном списке порядок элементов остаётся прежним: print(classroom) # Вывод: ['Спортзал', '12А', 'Компьютерный класс']
Однако при сортировке списка как с помощью метода list.sort(), так и с помощью функции sort(), важно следить затем, чтобы все элементы сортируемого списка были одного типа данных, иначе будет вызвано исключение ТуреЕггог:
cards = [6, 7, "Червгзый Туз"]
sorted_cards = sorted(cards)
print(sorted_cards)
#	Ошибка: ТуреЕггог: '<' not supported between instances of 'str' and 'int'
Как и метод list.sort() функция sorted() принимает необязательные параметры reverse и key. задающие направление сортировки и функцию, применяемую к каждому элементу.
Например, отсортируем список овощей по убыванию:
vegetables = [ Капуста", "кукуруза", "Перец", 'помидор"] sorted_vegetables = sorted(vegetables, reverse=True) print(sorted_vegetables)
#	[ 'помидор', 'кукуруза', 'Перец', 'Капуста']
При сортировке в лексикографическом порядке большое значение имеет регистр букв, но мы можем передать параметру key метод приведения строки к нижнему регистру - str. lower().
sorted_vegetables_2 = sorted(vegetables, key=str.lower, reverse=True) pr’int (sorted_vegetables_2)
#	Вывод: ['помидор', 'Перец', 'кукуруза', 'Капуста']
Здесь перед сортировкой строки приводятся к нижнему регистру и только потом сортируются в лексикографическом порядке.
Примеры
Пример 1. Приоритетность заказов
Программа добавляет новый VIP-заказ в начало списка обычных заказов:
Коллекции 208
orders = ['Заказ #101", "Заказ #102"J
vip_order = "VIP-Заказ #200"
orders.insert(0, vip_order) # Вставка на первое место (по индексу в) print('Очередь заказов , orders)
Вывод:
Очередь заказов: ['VIP-Заказ #200', 'Заказ #101', 'Заказ #102']
Пример 2. Обработка вызовов
Система обрабатывает вызовы в колл-ценгре по принципу «последний
пришёл - первый ушёл»:
calls = ["Звонок #1", "Звонок #2", "Звонок #3"]
while calls:
current_call = calls.рор() # Извлекаем с конца рг1п1("Обрабатывается:", current_call)
print('Очередь пуста")
Вывод:
Обрабатывается: Звонок #3
Обрабатывается: Звонок #2
Обрабатывается: Звонок #1 Очередь пуста
Пример 3. Ввод команд для быстрого досту па
Электронный журнал позволяет пользователю ввести команды для быстрого доступа и запрашивает эти команды до ввода слова «стоп», написанного в любом регистре. После сохранения команд система очищает список с ними: commands = []
# Команды перестанут запрашиваться и сохраняться после ввода команды "стоп" в любом регистре
newcommand^ inpur( ззедите команду: )
while new_command.lower() != "стоп":
C ommands.append(new_command)
new_command= input("Введите команду: ')
print(f‘Идет сохранение {len(commands)} команд... Команды сохранены1 ) commands.clear()
print(f"He сохранённые команды: {commands}")
Вывод:
сведите команду: Выделить текст красным
Введите команду: Рассчитать средний балл
Коллекции 209
Введите команду: Написать замечание
Введите команду: сТОп
Идёт сохранение 3 команд... Команды сохранены!
Несохраненные команды: []
Метод list. clear() работает быстрее, чем создание нового пустого списка files = [].
I11OI и
J Функция reversed() возвращает обратный итератор - объект, обеспечивающий перебор элементов итерируемого объекта в обратном порядке. Его можно преобразовать в коллекцию с помощью специальных функций, например, list() или tuple().
Функция sorted () возвращает отсортированный список из элементов итерируемого объекта. Можно задать как направление сортировки, так и функцию, которая будет применяться к каждому элементу итерируемого объекта.
Метод list.append(item) list.insert(index, item) list.extend(iterable) list.remove(item) list.pop(index=-l)
list.clear() list.reverse() list.sort(key=None, reverse=False)
Таблица 14 - Методы списков
Описание
Добавляет в конец списка list объект item
Вставляет объект item в список list по индексу index
Расширяет список list всеми элементам коллекции iteraole
Удаляет из списка list первый найденный объект item
Удаляет и возвращает из списка list элемент по индексу index
Удаляет все элементы списка list
Изменяет порядок элементов списка list на обратный Сортирует элементы списка list по возрастанию или по убыванию
Задания для самопроверки
1.	Как метод list. append() добавляет в список в другой список?
2.	Дан список statues = ("Статуя Ьдинства", "Статуя Свободы"]. Вставьте
Коллекции 210
строку "Рабочий и колхозница" по индексу 1 и выведите полученный список на экран.
3.	Какие два метода используются для удаления элементов из списка? Чем они отличаются?
4.	Дан список numbers = [1, 2, 3, 4, 5]. Удалите из него элемент с индексом 2 и выведите этот элемент на экран.
5.	Чем отличается функция sorted() от метода list.sort()?
6.	Запросите у пользователя две строки и преобразуйте их в списки, используя в качестве разделителя пробел. Объедините эти списки в один, отсортируйте его элементы по возрастанию их длины и выведите итоговый список на экран.
Пример входных данных	Пример выходных данных
Ой! Потерялись!	['Ой!', 'Эй!', 'Ау!', 'Ауу!', 'Потеря-
Эй! Ау! Ауу!	лись 1 ’ ]
Солнце светит так ярко.	['так', 'ярко.', 'Солнце', 'светит', 'пого-
Сегодня хорошая погода	да', 'Сегодня', 'хорошая']
Купи! Ну купи1 Купим слона	['Ну', 'Купи!', 'купи!', 'Купии', 'слона']
3.9 МНОЖЕСТВА
Множества в отличие от списков и кортежей могут хранить объекты только неизменяемого типа данных, например, числа или строки. Также они обладают двумя ключевыми особенностями: неупорядоченностью и уникальностью. К элементам множества нельзя обратиться по индексу, гак как они не хранятся в каком-либо определенном порядке, а также множества игнорируют повторяющиеся элементы, оставляя только уникальные значения.
При этом множества изменяемы, поэтому большинство их методов изменяют исходное множество и возвращают None.
Создание множества
Существует несколько способов создания множеств в Python.
Фигурные скобки для создания множества
Множество создаст перечисление элементов через запятую внутри фигурных скобок:
Коллекции 211
berries = { Рябина', "Черемуха1, "Земляника"}
Функция set() для создания множества
Любую встроенную коллекцию можно преобразовать в множество с помощью функции set().
Функция	set(iterable)
Описание	Создаёт множество из всех элементов коллекции iterable
Параметры	• iterable - итерируемый объект для преобразования в множество
Возвращаемое значение	Множество
Такое множество будет содержать все уникальные элементы этой коллекции, а повторяющиеся элементы будут проигнорированы:
numbers = [1, 2, 2, 3, 3, 3] print(set(numbers))
#	Вывод: {1, 2, 3}
Так же как функция list() разбивает строку на список символов, так и функция set () разбивает строку на множество символов:
plant = "Кукуруза" print(set(plant)) # Вывод: {'К', 'р', 'а', ‘у’, 'з', 'к'}
Так как элементы в множестве не упорядочены, то их порядок может отличаться при каждом запуске программы:
food = ("Хлеб", "Масли , "Хлеб", "Масло", "Джем") food_set = set(food) print(food_set)
#	Вывод (возможный): {'Хлеб', ‘Масло’, 'Джем'}
#	Вывод (возможный): {Масло, 'Хлеб', 'Джем'}
Создание нустого множества
Пустое множество создаёт функция set() без аргументов: menu = set()
Однако пустые фигурные скобки создают пустой словарь, а не множество: menu = {} print(type(menu))
Коллекции 212
# Вывод' 'class 'diet'?
Добавление элемента в множество
Метод set.add() добавляет один элемент в множество. При этом следует помнить о том, что множества хранят только уникальные значения.
Мет од	set.add(item)
Описание	Добавляет в множество set объект item
Параметры	item - объект, который нужно добавить в множество
Возвращаемое значение	None
Элемент будет добавлен только, если он отсутствует в множестве, иначе множество останется неизменным:
transport = {'Автобус', "Трамваи’, "Такси'}
transport.add( Метр  ) print(transport)
# Вывод: {'Метро'j 'Автобус', 'Такси', 'Трамвай'}
transp-"irt.add( Автибус") print(transport)
# Вывод: {'Метро', 'Автобус', 'Такси', 'Трамвай'}
Но если нужно добавить сразу несколько элементов, то на помощь приходит метод set.update().
Метод	set.update(*iterable)
Описание	Добавляет в множество set все элементы коллекций *iterable
Параметры	• *iterable - последовательность итерируемых объектов, все элементы которых нужно добавить в множество
Возвращаемое значение	None
Параметр *iterable означает, что мы можем передать данному методу несколько коллекций, перечислив их через запятую. Поэтому в существующее
множество мы можем как все элементы одного списка:
Коллекции 213
weather = {"Мокрый снег", "Град"., "Дождь }
rain = ["Дождь", "Морось', "Ливень"]
weather.update(rain)
print(weather)
#	Вывод: {‘Мокрый снег', 'Град', 'Дождь', 'Ливень', 'Морось'}
Так и одновременно все элементы кортежа и значения словаря:
weather = {"Ясно", "Пасмурно , "Переменная облачность'}
wind = ("Слабый ветер’, "Умеренный ветер")
temperature = {ЗИ: "Жарко", 21: "Комфортно , -40: "Холодно"}
weather.update(wind, temperature.values())
print(weather)
#	Вывод: {'Ясно', 'Холодно', 'Комфортно', 'Переменная облачность', 'Жарко', ‘Умеренный ветер', 'Слабый ветер', 'Пасмурно'}
Удаление элементов из множества
Из множества можно удалить элемент но значению, а также удалить случайный элемент. Кроме этого, можно полностью очистить множество, удалив все его элементы.
Удаление элемента из множества по значению
Для удаления из множества элемента по его значению существует два метода - set.remove() и set.discord(). Ключевое различие между этими двумя методами заключается в их поведении, когда элемент в множестве не найден:
о метод set. remove () в таком случае вызовет исключение Key Error, что приведёт к прерыванию выполнения программы;
о метод set .discard() просто ничего не делает.
Методы	set.remove(item) set.discard(item)
Описание	Удаляют объект item из множества set
Параметры	item - объект, который нужно удалить
Возвращаемое значение	None
Например, используем метод set.remove() и удалим строку "два" из следующего множества:
numbers = {1, 2, 3, "один1, "два , ’три"}
Коллекции 214
numbers.remove(l)
print(numbers)
#	Вывод- {2, 3, 'один', 'три', 'два'}
Однако если мы попробуем удалить элемент, которого нет в множестве, то будет вызвано исключение Key Error:
numbers.remove("пять1 )
print(numbers)
#	Ошибка: KeyError: 'пять'
В таком случае следует использовать метод set .discard(), который перед удалением проверяет наличие элемента в множестве:
numbers.discard(’пять")
print(numbers)
#	Вывод: {2, 3, 'один', 'три', 'два'}
Так как элемент "пять" отсутствует в множестве, то исходное множество остаётся неизменным.
Удаление случайного элемента множества
Для удаления (и возвращения) случайного элемента множества предназначен метод set.pop().
Методы	set. pop ()
Описание	Удаляет и возвращает случайный элемент множества set
Возвращаемое
Удаляемый элемент
значение
Как и метод списков list. рор(), данный метод не просто удаляет элемент, а возвращает его:
mail = {"mail.ru", "yandex.ru", "gmail.com }
deleted_mail = mail.pop()
print ({""Удалённый элемент - {deletedjnail}")
#	Вывод: Удаленный элемент - gmaiL.com
Удаление всех элементов множества
Также можно полностью очистить множество, используя метод set.clear().
Коллекции 215
Метод	set.clear()
Описание	Удаляет все элементы множества set
Возвращаемое значение	None
В таком случае множество станет пустым:
video = {'rutube', "youtube")
video.clear() print(video)
# вывод: {}
Замороженные множества
Кроме обычных множеств, которые изменяемы, в Python также представ' лена их неизменяемая версия, создаваемая функцией frozenset().
Функция	frozenset(iterable)
Описание	Создаёт замороженное (неизменяемое) множество из всех элементов коллекции iterable
Параметры	• iterable - коллекция для преобразования в множество
Возвращаемое значение	Замороженное (неизменяемое) множество
Как и в обычное множество (set), в замороженное множество (frozenset) можно преобразовать любую коллекцию:
frozen_numbers = frozenset([1, 2, 3, 3, 2])
print(frozen_number$)
#	вывод: frozenset({l, 2, 3})
Такие множества обладает всеми свойствами множеств, но не поддерживает методы, изменяющие содержимое, например, set .remove () или set.add():
frozennumbers = frozenset([1, 2, 3, 3, 2])
frozen_numbers.add(4)
#	Ошибка: AttributeError: 'frozenset' object has no attribute 'add'
Благодаря неизменяемости замороженных множеств они могут быть использованы в качестве ключей словаря или элементов другого множества.
Например, элементом множества не может быть список, так он изменяем.
Коллекции 216
однако его можно преобразовать в замороженное множество:
numbers = {1, 2, 3, 4, 5}
big_numbers = [100000, 100001, 1О0О02]
fro?en_big_numbers = frozenset(big_numbers)
numbers.add(f r-ozen_big_numbers)
print(numbers)
it Вывод: {1, 2, 3, 4, 5, frozenset({100000, 300001, 100002})}
Примеры
Пример 1. Нормализация тегов
Платформа для ведения блога обеспечивает уникальность тегов и единообразие их написания (в нижнем регистре):
raw_tags = ["Python", "python", "Алгоритмы", "базы данных", "Базы Данных"] normalized_tags = set() # Пустое множество для обработанных тегов
#	Нормализация тегов for tag in raw_tags: normalized_tags.add(tag.lower()) print("Уникальные теги;", normalized_tags)
Вывод:
Уникальные теги: {'алгоритмы', 'python', 'базы данных'}
Пример 2. Управление активными сессиями пользователей
Программа отслеживает уникальные JP-адреса. с которых пользователи в настоящее время подключены к сервису. Это критично для мониторинга нагрузки, обнаружения аномалий и зашиты от DDoS-атак При успешной авторизации нового пользователя, его IP-адрес безопасно добавляется в множество, хранящее все активные IP-адреса. Также реализуется возможность добавления сразу нескольких IP-адресов. Когда пользователь выходит или его сессия истекает. его IP-адрес удаляется:
active_ips = { 10.0.0.5 , "172.16.0.1'}
#	Добавление одного IP
active_ips.add("192.168.1.1")
#	Добавление нескольких IP
new_batch = {'10.У.0.2", "192.168.1.5'}
active_ips.update(new_batch)
print(f Подключенные IP-адреса: {activeips} )
it Несколько IP-адресов отключаются
disconnectedjps = ["10.0.0.5м, "192.168.1.99']
print(f"Отключаем адреса {disconnected_ips}..	)
Коллекции 217
active_ips.discard( 10.0.0.5") # Удаление существующего IP active_ips.discard( 192.168.1.99") # Игнорирование несуществующего IP print(f Подключенные ТР-адреса: {active_ips}")
Вывод:
Подключенные IP-адреса: {'192.168.1.5', '10.0.0.2', '10.0.0.5', '172.16.0.1', '192.168.1.1'}
Отключаем адреса ['10.0.0.5', ‘192.168.1.99’]...
Подключенные IP-адреса: {'192.168.1.5’, '10.0.0.2', '172.16.0.1', '192.168.1.1'}
Пример 3. Очистка диспетчера задач
Определённые события в системе имеют одинаковый приоритет, поэтому при для их обработки программа берёт случайное событие из множества событий, после чего удаляет его. В конце работы программа очищает всё множество событий:
pending_events = { Ошибка базы", "Новое подключение", 'Сбой авторизации", “Предупреждение  нагрузке1}
#	Обработка случайного события
processed_event = pending_events.рор()
print(f"Обработано событие: {processed_event}' )
print(f'Остались события: {pendingevents}", end="\n--------\n")
#	Обработка ещё одного случайного события
processed_event = pendingevents.рор()
print(f Обработано событие; '{processed_event}'')
print(f"Остались события: {pending_events}“, end="\n - -- -\n")
#	В конце работы очищаем всё pending_events.clear()
print (-(-"События в конце работы: {pending_events} )
Вывод:
Обработано событие: 'Новое подключение'
Остались события: {'Ошибка базы', 'Предупреждение о нагрузке', 'Сбой авторизации '}
Обработано событие: 'Ошибка базы'
Остались события: {'Предупреждение о нагрузке', 'Сбой авторизации'}
События в конце работы: set()
Ито1 и
S Функция set () преобразует другую коллекцию в множество.
Коллекции 218
Множества изменяемы, но их элементами могут быть только объекты неизменяемою типа данных.
Все элементы в множестве уникальны, а повторяющиеся значения игнорируются,
J Элементы множества не упорядочены и к ним нельзя обратиться напрямую.
Функция frozenset() преобразует другую коллекцию в замороженное (неизменяемое) множество.
Таблица 15 - Методы множеств
Метод	Описание
set.add(item) set.update(*iterable)	Добавляет в множество set объект item Добавляет в множество set все элементы коллекций *iterable
set.remove(item)	Удаляет объект item из множества set. Если объект не найден, то вызывает исключение KeyError
set.discard(item)	Удаляет объект item из множества set. Если объект не найден, то ничего не делает
set.popQ	Удаляет и возвращает случайный элемент множества set
set.clear()	Удаляет все элементы множества set
Задания для самопроверки
1.	Чем множества отличаются от списков и кортежей?
2.	Дан список duplicates = [1, 2, 2, 3, 4, 4, 5, 5, 5]. Преобразуйте этот список в множество для удаления дубликатов, а затем снова преобразуйте полученное множество в список и выведите его на экран.
3.	Создайте пустое множество empty_set, добавьте в него элементы 10 и 20 и выведите полученное множество на экран.
4.	Дано множество tasks = {"Работа", "Учеба", "Сон", "Спорт"} Удалите случайный элемент множества, присвойте его переменной completed_task и выведите значение этой переменной на экран.
5.	Дан список languages = ["Python", "JavaScript", "C++", "Python"]. Создайте из этого списка неизменяемое множество и выведите его на экран. Можете ли вы добавить в него новый элемент "Go"? Почему?
Коллекции 219
3.	10 ОПЕРАЦИИ НАД МНОЖЕСТВАМИ
Множества в Python пришли из математики, где над ними можно совершать такие операции как объединение и пересечение, а также находить разность и симметричную разность.
Рассмотрим два множества: красные яблоки и зелёные груши. При этом оба множества содержат жёлтые яблоки и груши:
setl - {"Красные яблоки’, "Желтые яблоки , "Жёлтые груши }
set2 = {"Зелёные груши", "Желтые груши”, "Жёлтые яблоки"}
Объединение двух множеств - это новое множество, которое содержит все элементы обоих множеств. В математике объединение двух множеств А и В обозначается как A U В.
Для того, чтобы наглядно представить множества и отношения между ними, используем крут и Эйлера. Каждый круг представляет одно множество, а область внутри него - его элементы. Перекрывающиеся области между кругами показывают элементы, которые являются общими для этих множеств.
Рисунок 17. Объединение множеств setl | set?
На кругах Эйлера объединение двух множеств - это область, которая включает все элементы, принадлежащие хотя бы одному из этих множеств, то есть это общая закрашенная площадь всех кругов,
В Python найти объединение двух множеств позволяет метод set.inter-section() и оператор |. Причём результаты как метода, так и оператора, полностью совпадают друг с другом.
Коллекции 220
Метод
setl.union(set2)
Описание
Параметры
Возвращаемое значение
Возвращает множество, содержащее все элементы множеств setl и set2
• set2 - множество, с которым объединяется множество setl
Множество
Новое множество содержит как красные яблоки и зелёные груши, так и жёлтые яблоки и груши. То есть все элементы, присутствующие хотя бы в одном из множеств setl или set2. но без повторений:
setl = {"Красные яблоки”, "Желтые яблоки , "Жёлтые груши }
set2 = {"Зелёные груши", "Жёлтые груши", "Желтые яблоки"}
union_set = setl.union(set2) # равносильно setl I set2 print(union_set)
#	Вывод: {'Желтые яблоки', 'Красные яблоки', 'Зелёные груши', 'желтые груши’}
Пересечение двух множеств - это новое множество, которое содержит общие элементы обоих множеств. В математике пересечение двух множеств А и В обозначается как Л А В.
Рисунок 18. Пересечение множеств setl & set2
На кругах Эйлера пересечение множеств - это область, которая находится внутри пересекающихся кругов и представляет собой общие элементы, принадлежащие одновременно всем этим множествам.
Найти пересечение двух множеств позволяет метод set. intersection^) и оператор &
Коллекции 221
Метод
setl. intersection set2)
Описание	Возвращает множество, содержащее только те элементы множества setl, которые также принадлежат множеству set2
Параметры	• set2 - множество, с которым находится пересечение множества setl
Множество
Возвращаемое значение
Новое .множество содержит только жёлтые яблоки и груши, так как только они присутствуют одновременно в обоих множествах setl и set2.
setl = {"Красные яблоки' > "Жёлтые яблоки , "Жёлтые груши }
set2 = {"Зелёные груши", "Жёлтые груши", "Жёлтые яблоки"}
intersection_set = setl.intersection(set2) # равносильно setl & set2 print(intersection_set)
#	Вывод: {'Жёлтые груши', 'Жёлтые яблоки'}
Разность двух множеств - это новое множество, которое содержит только те элементы первого множества, которых нет во втором. В математике разность двух множеств А и В обозначается как А \ В
Рисунок 19. Разность множеств setl - set2
На кругах Эйлера разность множеств - это та часть круга первого множества, которая находится вне пересечения со вторым.
Найти разность двух множеств позволяет метод set. difference() и оператор
Коллекции 222
Метод
setl.difference(set2)
Описание
Возвращает множество, содержащее только те элементы множества setl, которые отсутствуют в множестве set2
Параметры
• set2 - множество, с которым находится разность множества setl
Возвращаемое значение
Множество
При вычислении разности множеств большое значение имеет порядок мно-жес гв. Так. если мы находим разность setl - set2, то новое множество содержит только уникальные элементы множества setl, то есть красные яблоки, а если между set2 - setl, то уникальные элементы множества set2, то есть зелёные груши:
setl = {"Красные яблоки', "Жёлтые яблоки", "Жёлтые груши }
set2 = {"Зелёные груши", "Жёлтые груши", "Жёлтые яблоки"} prinT(setl.difference(set2)) # равносильна setl - set2 # Вывод: {'Красные яблоки'} print(set2 - setl) # равносильно set2.difference(setl) # Вывод: {‘Зелёные груши'}
Симметричная разность двух множеств - это новое множество, которое не содержит общие элементы обоих множеств. В математике симметричная разность двух множеств А и В обозначается как АД В.
setl  JU
Рисунок 20. Симметричная разность множеств setl Л set2
На кругах Эйлера симметричная разность множеств - это область, которая включает в себя все элементы, принадлежащие только одному из множеств, и Коллекции 223
исключает общую пересекающуюся часть.
Найти симметричную разность двух множеств позволяет метод set.sym-metric_difference() и оператор Л.
setl.symmetric_difference(set2)
Возвращает множество, содержащее только те элементы множеств setl и set2, которые не принадлежат одновременно обоим множествам
• set2 - множество, с которым находится симметричная разность множества setl
Множество
Метод
Описание
Параметры
Во шращаемос значение
Новое множество содержит только красные яблоки и зелёные груши, так как исключаются все общие элементы множеств setl и set2:
setl = {"Красные яблоки", "Жёлтые яблоки", "Жёлтые груши"}
set2 = {"Зелёные груши", "Желтые груши", "Жёлтые яблоки"}
print(setl.syinmetric_difference(set2)) # равносильно setl Л set2
#	Вывод: {'Зеленые груши', 'Красные яблоки'}
Операторы с присваиванием для операций над множествами
Как и арифметические операторы, операторы для работы с множествами можно совмещать с оператором присваивания () и таким образом сразу присваивать новое значение переменной с исходным множеством:
setl = {"Красные яблоки', "Жёлтые яблоки , "Жёлтые груши }
set2 = {"Зелёные груши", "Жёлтые груши", "Жёлтые яблоки"}
setl |= set2 # Объединение print(setl)
#	Вывод: {'Жёлтые яблоки', 'Красные яблоки', 'Зелёные груши', 'Жёлтые груши'}
setl &- set2 # Пересечение
print(setl)
#	Вывод: {‘Жёлтые груши', 'Желтые яблоки'}
setl -= set2 # Разность
print(setl)
#	Вывод: {‘Красные яблоки'}
setl Л= set2 # Симметричная разность
Коллекции 224
print(setl)
#	Вывод: {‘Зеленые груши', 'Красные яблоки'}
Во всех случаях исходное множество setl перезаписывается новым множеством.
Сравнение множеств
Как числа и строки, множества поддерживают операцию сравнения, позволяя определять, является ли одно множество частью другого.
Например, у нас есть множество овощей, а также множество ингредиентов для овощного салата:
vegetables = { Моркивка’, "Картошка"}
salad = {'Морковка", "Картошка", "Яйца", "Майонез", "Соль'}
Так как все элементы множества vegetables присутствуют в множестве salad, то множество vegetables является подмножеством множества salad, а множество salad - его надмножеством.
То есть подмножест во - это множество, все элементы которого также принадлежат другому множеству, называемому надмножеством. Поэтому подмножество всегда меньше или равно надмножеству, а надмножество всегда больше или равно подмножеству. Это позволяет использовать уже знакомые нам операторы сравнения:
print(salad >= vegetables)
#	Вывод: True
print(vegetables <= salad)
#	Вывод: True
print(salad == vegetables)
#	вывод: TaLse
print(vegetables != salad)
#	Вывод: True
Операторам сравнения => и <= соответствуют свои методы:
о метод setl.issuperset(set2) соответствует оператору => (больше или равно) и проверяет содержит ли множество setl множество set2;
о метод setl.issubset(set2) соответствует оператору <= (меньше или равно) проверяет является ли множество setl частью множества set2
Коллекции 225
Метод	setl.issuperset(set2)
Описание	Возвращает True, если множество setl содержит все элементы множества set2, иначе - False
Параморы	set2 - множество, с которым сравнивается множество setl
Возвращаемое значение	True или False
Метод	setl.issubset(set2)
Описание	Возвращает True, если все элементы множества setl содержатся в множестве set2, иначе - False
Параметры	set2 - множество, с которым сравнивается множество setl
Возвращаемое значение	True или False
Результат использования этих методов совпадает с результатом использования операторов сравнения-vegetables = { Моркг-вка'"Картошка } salad = { Морковка", "Картошка", "Яйца", "Майонез1, "Соль'} print(salad.issuperset(vegetables)) # Вывод: True pri nt(vegetables.i ssubset(salad)) # Вывод: True
Множества можно не только сравнить, но и проверить наличие общих элементов. Для этого предназначен метод set.isdisjoint().
Метод	set.isdisjoint(iterable)
Описание	Возвращает True, если множество set не имеет общих элементов с объектом iterable, иначе - False
Параметры	iterable - итерируемый объект, в котором проверяется наличие общих элементов с множеством set
Возвращаемое значение	True или False
Коллекции 226
Метод set .isdisjoint() возвращает True, только еели они не имеют общих элементов:
vegetables = { Морковка , “Картошка'1}
fruits = [ Ананас", "Банан1', “Дыня"] print(vegetables.isdisjoint(fruits)) # вывод: True
й Преобразуем во множество для операций над множествами
fruits_set = set(fruirs)
coinmonset = vegetables.intersection(fruits)
p rint(common _set)
it Вывод: set() <-- пустое множество
Результат этого метода необязательно совпадает с результатом проверки на неравенство (!=). Так как даже если множества не равны, то они всё равно могут иметь общие элементы:
salad = {"Яблоко", "Морковка }
vegetables = { Морковка , "Картошка"}
print(sal ad.isdisjoint(vegetables))
#	Вывод: FaLse
print(salad != vegetables)
#	Вывод: True
Примеры
Пример 1. Полный список пользователей
Пользователи системы управления магазином могут иметь доступ к панели администратора, базе данных или к ним обеим одновременно. Программа объединяет множества пользователей обоих ресурсов и возвращает полный список уникальных пользователей.
#	Пользователи, имеющие доступ к панели администратора admin_users = {"nicholas_roman->v , “borisj’oduncv-, "yuri_zvenigorod', sup port"}
#	Пользователи, имеющие доступ к базе данных
db_users = {"boris_godunov", "dmitry_anuchin", "yuri_zvenigorod", "ser-vice_bot"}
#	Все уникальные пользователи (объединение множеств)
all_unique_users = admin_users | db_users Д Эквивалентна: admin_users.ип--ion(db_users)
print(Г"Пользогатели с доступом к панели администратора: {admin_users}") print(f"Пользователи с доступом к базе данных: {db_users}") print(f Все уникальные пользователи: {all_unique_users}')
Коллекции 227
Вывод:
Пользователи с доступом к панели администратора: {'nicholas_romanov', 'boris_godunov', 'yuri_zvenigorod', 'support'}
Пользователи с доступом к базе данных: {'boris_godunov', 'yuri_zvenigorod'r 'dmitry_anuchin'} 'service_bot'}
Все уникальные пользователи: {'nicholas_romanov', 'boris_godunov', 'yuri_zvenigorod', 'service_bot', 'dmitry_anuchin', 'support'}
Пример 2. Анализ поведения иилыователей
В интернет-магазине в процессе анализа поведения клиентов сравнивают наиболее частые действия новых и постоянных клиентов:
new_users_actions = {"Поиск , "Просмотр", Добавление в корзину"} loyal_users_actions = {"Просмотр , "Покупка", "Отзыг }
#	Какие действия уникальны для новых клиентов? (разность множеств) unique_to_new = new_users_actions - loyal_users_actions
рг1пТ(Г"Уникальные дейстпия новых клиентов- {unique_to_new} )
#	Какие действия совершают только постоянные клиенты? (разность множеств) unique_to_loyal = loyal_users_actions - new_users_actions
print(f"Уникальные действия постоянных клиентов: {unique_to_loyal}")
#	Какие обшие паттерны поведения? (пересечение множеств) common_actions = new_users_actions & loyal_users_actions print(f"Общие действия: {common_actions}’)
Вывод:
Уникальные действия новых клиентов: {'Добавление в корзину', 'Поиск'}
Уникальные действия постоянных клиентов: {'Покупка', 'Отзыв'} Общие действия: {'Просмотр'}
Пример 3. Проверка выбора фильтров
Онлайн-платформа объявлений о продаже и аренде недвижимости требует обязательный выбор нескольких фильтров:
required_subcategories = { Тип объекта", "Комнатность' }
user_input = { комнатность", "Цена ", "Площадь' }
#	Все ли обязательные фильтры присутствуют?
isvalid = userinput.issuperset(requiredsubcategories)
print(f"Указаны все обязательные фильтры: {is_valid} )
#	Какие категории отсутствуют?
missing = required_subcategories - user_input
print(f Отсутствуют фильтры: (missing if not is_valid else "нет'})
Коллекции 228
Вывод:
Указаны все обязательные фильтры: False Отсутствуют фильтры: {’Тип обьекта'}
Итоги
Таблица 16- Методы операций над множествами и сравнения множеств
Метод	Описание	Опе- ратор
setl.union(set2)	Возвращает множество, содержащее все элементы множеств setl и set2	1
setl.intersec-tion(set2)	Возвращает множество, содержащее только те элементы множества setl, которые также принадлежат множеству set2	&
setl.differ-ence(set2)	Возвращает множество, содержащее только те элементы множества setl, которые отсутствуют в множестве set2	
setl.symmet-ric_differ-ence(set2)	Возвращает множество, содержащее только те элементы множеств setl и set2. которые не принадлежат одновременно обоим множествам	Л
setl.issuper-set(set2)	Возвращает True, если множество setl содержит множество set2, иначе - False	>=
setl.issub-set(set2)	Возвращает True, если множество setl является частью множества set2, иначе - False	<=
setl.isdis-joint(iterable)	Возвращает True, если множество set не имеет общих элементов с объектом iterable. иначе - Fa] se	Нет
Задания для самопроверки
1,	Почему порядок множеств не имеет значения при их объединении, пересечении и симметричной разности, но критически важен при разности?
2.	Объясните ключевое различие между операцией разности (-) и операцией симметричной разности (Л) с точки зрения элементов, которые попадают в итоговое множество.
3.	Даны два множества set_a = {1, 2, 3, 4, 5} и set_b = {4, 5, 6, 7,
Коллекции 229
8}. Найдите и выведите на экран объединение, пересечение и симметричную разность этих множеств.
4.	Даны множества team_a = {"Иван", "Дмитрий", "Елена", "Ольга"} и team_b = {"Ольга", "Пётр", "Дмитрий", "Мария"}. Создайте и выведите на экран новое множество из участников, состоящих только в одной команде, а не в обеих.
5.	Даны множества colorsl = {"Красный", "Синий", "Желтый"} и colors2 = {"Синий", "Желтый"}. Проверьте, является ли colorsl надмножеством colors2. и является ли colorsl подмножеством colors2. Выведите оба результата на экран.
3.11 СЛОВАРИ
Списки и кортежи подходят для хранения последовательности элементов, но что, если нам удобнее обращаться к элементам но какому-то имени, а не ио порядковому номеру? Для этого в Python предназначены словари. Вместо того чтобы хранить элементы по порядку, словари хранят пары «ключ-значение». Они похожи на настоящие словари, где у каждого слова (ключа) есть свое определение (значение).
Ключи словаря должны быть уникальными и представлены неизменяемым типом данных, например, числом, строкой, кортежем или даже замороженным множеством. На значения словаря никакие ограничения не накладываются.
Создание словаря
Существует несколько способов создания словарей в Python.
Однако при создании словаря всегда следует помнить о том. что ключи должны быть не только уникальными, но и представлены неизменяемым типом данных. Поэтому ключи не могут быть списками, множествами и другие словарями. но могут быть любыми числами, строками, кортежами и замороженными множествами.
Значениями же могут быть любые типы данных, в том числе, другие словари или вложенные коллекции.
Фигурные скобки для создания словаря
В самом простом случае словарь создаётся с помощью фигурных скобок, внутри которых через занятую перечисляются пары ключ: значение:
Коллекции 230
phone_book = {
"Мастер": "+79315555555",
"Маргарита": "+79637777777"
}
Функция dict() для создания словаря
Функция diet() создаёт словарь из пар «ключ-значение», которые могут быть переданы как именованные аргументы ключ=значение или последовательность кортежей с парами (ключ, значение).
Функция
dict(keyl=valuel, key2=value2,...)
dict([(keyl, valuel), (key2, value2),...])
Описание
Создаёт словарь из именованных аргументов или последовательности кортежей
Параметры
•	key=value - именованные аргументы ключ=значение
•	(key, value) - кортежи с парами (ключ, значение)
Возвращаемое значение
Словарь
Создание словаря с помощью именованных аргументов
Именованные аргументы позволяют передать функции значение по его имени. Например, мы уже передавали функции print() аргумент sep по его имени:
print( Одна овечка", 'Дае свечки , "Три овечки", sep=" * ")
#	Вывод: Одна овечка * Две овечки * Три овечки
Функция dict(), в свою очередь, принимает аргументы с любыми именами. В таком случае имя аргумента будет ключом словаря, а сам аргумент - его значением:
phone_book = dict(Master="+79315555555", Margarita="+79637777777 ) print(phone_book)
#	Вывод:{'Master': '+79315555555', 'Margarita': '+79637777777'}
Однако так как имена переменных (аргументов) могут содержать только английские буквы, то и ключи в таком словаре будут написаны английскими буквами.
Создание словаря из последовательности коргежей
Также функции dict() можно передать последовательность (например.
Коллекции 231
список или множество) кортежей, где каждый внутренний кортеж состоит из двух элементов: ключа и соответствующего ему значения:
pHne_book_list = [
(’Мастер", "+79315555555 ),
(’Маргарита", "+79637777777")
]
phone_book = dict(phone_book_list)
print(phone_book)
#	Вывод: {'Мастер': ‘+79315555555 ', 'Маргарита': '+79637777777'}
Создание словаря из двух последовательностей
Если у нас есть две отдельные последовательности: одна содержит ключи, а другая - значения, то напрямую из них нельзя создать словарь, однако их можно попарно объединить с помощью функции zip(), и после этого уже передать функции dict().
Функция zip() создаёт итератор кортежей, где каждый кортеж содержит i-й элемент из каждого переданного объекта.
Функция	zip(*iterable)
Описание
Параметры
Возвращает итератор кортежей, в котором каждый кортеж содержит элементы переданных коллекций, стоящие на одинаковых позициях
•	*iterable - последовательность итерируемых объектов, элементы которых требуется объединить
Необязательные параметры:
•	strict - логическое значение, определяющее поведение
при разной длине итерируемых объектов. По умолчанию strict=False
Возвращаемое значение
Итерат ор кортежей
Итератор - это объект, обеспечивающий последовательный доступ к элементам итерируемого объекта. Поэтому его нельзя вывести на экран, но можно передать функции diet() для создания словаря:
names = ['Мастер , "Маргарита"]
phones = [ +79315555555", "+79637777777"]
phone_book = dict(zip(names, phones))
print(phone_ book)
Коллекции 232
#	Вывод {'Мастер': '+79315555555 ', ‘Маргарита': '+79637777777'}
Последовательности ключей и значений должны иметь одинаковую длину, так как функция zip() останавливается, когда исчерпана самая короткая коллекция:
students = ["Курчатов И.В.", "Сахаров А.Д.", "Харитон Ю.Б."] grades = [
{"Теплотехника": 4, "Химия": 5},
{"Теплотехника”: 5, "Химия": 4} ] students_dict = dict(zip(students, grades)) print(students_dict)
#	Вывод: {'Курчатов И.В.': {'Теплотехника': 4} 'Химия': 5}, 'Сахаров А.Д. ': {'Теплотехника': 5j 'Химия': 4}}
Но с помощью параметра strict=True можно изменить это поведение и в таком случае вызывать исключение ValueError.
Если функции zip() передать не два. а три списка, то каждый кортеж будет содержать гри элемента, а если четыре списка - то четыре элемента и так далее. Однако словарь можно создать только на основе итератора, созданного из двух коллекций: ключей и значений.
Метод dict.fromkeys() для создания словаря
Метод diet.fromkeys() создаст словарь с заданными ключами и единым значением для всех ключей.
Метод	diet.fromkeysCiterable, value-None)
Описание
Параметры
Возвращает словарь, в котором ключи берутся из итерируемого объекта iterable, а все значения value одинаковы
•	iterable - итерируемый объект, содержащий ключи словаря
Необязательные параметры:
•	value - значение для всех ключей, по умолчанию
value=None
Возвращаемое значение
Словарь
Коллекции 233
Этот метод принимает последовательность ключей словаря и каждому
ключу присваивает одинаковое значение (по умолчанию - None):
students = ["Р'машкина А.Г. , "Жасмино* а Д.Е. , "Пионов Г.У. ] students_grades_l = dict.fromkeys(students) print(students_grades_l)
#	Вывод: {‘Ромашкина А.Г. ': None, 'жасминова Д.Е. ': None, 'Пионов Г.У.
None }
Значение по умолчанию изменяет параметр value
students_grades_2 = dict.fromkeys(students, 0)
print(students_grades_2)
#	Вывод: {'Ромашкина А.Г. ': 0, 'Жасминова Д.Е. ': в, 'Пионов Г.У. ': 0}
Если в качестве значения по умолчанию передать изменяемый объект (например, список), то все ключи получат ссылку на один и тот же объект: students_grades_3 = dict.fromkeys(students, (]) students__grades_3["PoMaujKMHa А.Г. ].append(5) print(students_grades_3)
#	Вывод: {'Ронашкина А.Г. ': [5], 'Жасминова Д.Е. ': [5], 'Пионов ГУ.': [5]}
Изменение значения одного такого ключа приводит к изменению значений всех ключей, поэтому с изменяемыми объектами метод dict.fromkeys() следует использовать с осторожностью.
Создание пустого словари
Пустой словарь создаю! функция dict() без аргументов и пустые фигур-ные скобки:
ernperors_achievements = dict() emperors_achievements = {}
Получение элементов словаря
Обращение к элементам словаря похоже на обращение к элементам списка пли кортежа, но вместо индекса в квадратных скобках указывается ключ: subjects = {
"Математика": "Mathematics",
"Физика": "Physics", "Информатика": "Informatics"
}
print(subjects["Математика"J)
#	Вывод: Mathematics
Если элемент по указанному ключу отсутствует в словаре, то вызывается исключение КеуЕггог:
Коллекции 234
print(subjects[‘Русским язык"])
#	Ошибка: KeyError: 'Русский язык'
Метод diet.get() позволяет избежать этого, так как в гаком елучае возвращает значение по умолчанию.
Метод	diet.get(key, default^None)
Описание	Возвращает значение из словаря diet по ключу key или значение по умолчанию default • key - ключ, по которому требуется получить значение
Параметры	Необязательные параметры: • default - значение, которое будет возвращено, если ключ key отсутствует в словаре diet. По умолчанию de-fault=None
Возвращаемое значение	Значение по ключу key или по умолчанию
Теперь при обращении по нееуществующему ключу будет возвращено значение ио умолчанию - None.
subjects = {'Математика': "Mathematics",
"Физика": "Physics",
"Информатика": "Informatics" }
print(subjects.get("Русский язык')) # Вывод: None
Но его можно изменить с помощью параметра default:
print(subjects.get("Русский язык1, отсутствует перевод слова'))
#	Вывод: Отсутствует перевод слова
Метод diet.get() не изменяет словарь и значение default не добавляется в словарь по ключу key.
Однако существует метод dict.setdefault(), который не просто возвращает значение по ключу, а добавляет пару «ключ-значение» в словарь, если переданный ключ не был найден.
Коллекции 235
Метод	diet.setdefault(key, default=None) Возвращает значение из словаря diet по ключу key. Если
Описание	ключ не найден, добавляет в словарь элемент с ключом key и значением default • key - ключ, по которому требуется получить значение
Параметры	Необязательные параметры: • default - значение, которое будет возвращено, если ключ key отсутствует в словаре diet. По умолчанию default =None
Возвращаемое значение	Значение по ключу key
Если ключ есть в словаре, то он. как и метод dict.get(), возвращает его значение:
user = {"username": "grand_elf99", "email": "rurik99@example.ru"}
print(user.set.default(’username", "He указан ))
# Вывод: grund_eLf99
Но если ключ не найден, то метод diet.setdefault() сначала добавляет пару key: default в словарь, а затем уже возвращает значение по добавленному ключу:
age = user.setdefaultC’UojpacT')
print(age)
#	Вывод: None
print(user)
#	Вывод: {'Логин': 'grand_eLf99', 'Почта': 'rurik99@exampLe.ru'., 'Возраст': None}
По умолчанию значением не найденного ключа будет None, но параметр default позволяет это изменить:
age = user.setdefault("Возраст", 50)
print(age)
#	Вывод: 50
print(user)
#	Вывод: {'Логин': 'sauron4ever', 'Почта': 'bigLord@exampLe.ru', 'Возраст': 50}
Добавление и изменение элементов словаря
Для добавления нового элемента в словарь достаточно присвоить новое
Коллекции 236
значение новому ключу:
goods = {'Телевизор": 12500., "Стиральная машина": 31999}
goods[ Холодильник'] = 38499 print(goods)
#	Вывод: {'Телевизор': 12506, 'Стиральная машина’: 31999, 'Холодильник': 38499}
Ключи в словаре должны быть уникальными и каждый ключ соответствует только одному значению. Поэтому каждое новое значение перезаписывает предыдущее:
goods = {"Газовая плита": 27500, "Вентилятор": 999}
goods[ Вентилятор '] = 1999 print(goods[ "Вентилятор J) # Вывод: 1999 goods[ Вентилятор J = 2500 print(goods["Вентилятор J) # Вывод: 2506
Объединение словарей
Метод diet.update(), и соответствующий ему оператор |=, дополняют словарь элементами другого словарь. Они эквиваленты друг другу и обновляют словарь, добавляя пары «ключ-значение» из другого словаря или итерируемого объекта. Если ключи уже существуют, их значения перезаписываются.
diet.update(other)
Дополняет исходный словарь diet парами «ключ-значение» из объекта other. Если добавляемый ключ существует, то перезаписывает его значение
• other - словарь или последовательность кортежей (ключ, значение), значения которых требуется добавить в словарь diet
None
Таким образом можно добавить все элементы одного словаря в другой: rooms = {"Комната 12А": "Алексей", "Комната 12Б": ["Кирилл", "Денис"]} girls_rooms = {"Комната 13А": "Полина', "Комната 13Б": "Наталья"} rooms.update(girls_rooms) # Эквивалентно rooms /= girLs_rooms print(rooms)
Метод
Описание
Параметры
Возвращаемое значение
Коллекции 237
#	Вывод: {"Комната 12А": "Алексей", "Комната 12Б": ["Кирилл", "Денис"], "Комната 13А": "Полина", "Комната 13Б": "Наталья"}
Или дополнить словарь последовательностью кортежей с парами (ключ, значение) и именованными аргументами ключ=значение-
profile = {"username": ‘lulu } extra_data = [
("email , "sunshine_OBg)example.ru'), ("extra_email‘ , "kurcreva_c(a)example. ru") ]
profile |= extradata
orofile.update(age=17)
print(profile)
#	Вывод: {'username': 'LuLu', 'emaiL': [ ' sunstnne_0&<i>e.K.ampLe. ru ', 'Ko-roreva_o@exampLe.ru'], 'age': 17}
Однако метод diet .update() и оператор |= изменяют исходный словарь, что в некоторых случаях может быть нежелательным.
Объединить два словаря в один новый можно через распаковку словарей в новый словарь конструкцией {**dictl, **dict2} или с помощью оператора |. В обоих случаях создаётся новый словарь, а не изменяются исходные. При совпадении ключей используются значения из словаря dict2:
defaults = {"Цвет”: "Красный', "Размер": "М"}
user_settings = {‘Размер": "L", "Рукава": "Длинные"}
final_settings = defaults | user_settings
print(final_settings)
#	Вывод: {'Цвет'- 'Красный', 'Размер': 'L', 'Рукава': 'Длинные'}
Однако оператор | для объединения словарей появился только в Python 3.9, поэтому при использовании более старых версий языка словари объединяются через распаковку {**dictl, **dict2}:
finalsettingsl = {**defaults, **user_settings}
print(final_settings)
#	Вывод: {'Цвет': 'Красный', 'Размер': 'L', 'Рукава': 'Длинные'}
Удаление элементов словаря
Удалить элемент словаря можно только по ключу, но не по значению.
Удаление элемента по ключу с помощью оператора del
Оператор del в Python удаляет указанный объект, в том числе, элемент словаря:
flowers_meanings = {"Хризантема": "Дружба, "Кактус": "Схожесть характеров"}
Коллекции 238
del flowers_meanings[ 'Хризантема' ] print(flowers_meanings)
#	Вывод: {‘Кактус’: ‘Схожесть характеров '}
Оператор del удаляет ссылку на объект, уменьшая счетчик ссылок на него. Если счетчик ссылок становится равным нулю, объект удаляется сборщиком мусора.
Однако при удалении несуществующего элемента будет вызвано соответствующее исключение, например. Key Error для словарей:
del flowers_meanings[ ’Пионы"]
#	Ошибка: KeyError: ‘Тюльпаны красные’
Оператор del может удалять не только элементы словаря по ключу, но и элементы других коллекций, например, списка, а также целые переменные:
flowers = ["Роза', ’Фиалка", "Ромашка’]
del flowers[1] # Удаление элемента списка по индексу print(flowers)
#	Вывод: ['Роза', 'Ромашка']
del flowers # Удаление переменной
print(flowers)
#	Ошибка: Name Error: name 'fLowers' is not defined
Удаление и возвращение элемента
Если нужно не просто удалить, а ещё и вернуть удаляемый элемент, то для этого предназначен метод dict.pop().
dict.pop(key, default=None)
Удаляет из словаря diet элемент по ключу key и возвращает значение удаляемого элемента
•	key - ключ элемента, который требуется удалить
Необязательные параметры:
•	default - значение, которое будет возвращено, если ключ key отсутствует в словаре diet. По умолчанию de-fault=None
Удаляемое значение
Данный метод возвращает значение удаляемого элемента, но не ключ flowers_meanings = {
Метод
Описание
Параметры
Возвращаемое значение
Коллекции 239
"Кактус": "Схожесть характеров",
"Примула : "Объяснение в любви"
}
del -flowermeanings = flowers_meanings.pop( кактус") print(del_flower_meanings)
# Вывод: Схожесть характеров print(flowers_meanings)
# Вывод: {'Примула': 'Объяснение в любви'}
Также метод dict.popQ не вызывает исключение KeyError, а возвращает значение по умолчанию (None или другое указанное значение):
f lowers_meanirigs = {
"Кактус": "Схожесть характеров", "Примула": Объяснение в любви" }
del_Flower_meaning$ = flowers_meanings.pop( Одуванчик", "Элемент не найден") print(del_F1ower_meani ngs) # Вывод: Элемент не найден
Метод рор() используется для удаления и возвращения элемента в списках, множествах и словарях. Этот метод в Python пришёл из концепции стека - структуры данных, в которой элементы добавляются и удаляются только с одной стороны, а операция pop удаляет и возвращает последний элемент. Это похоже на стопку тарелок, с которой мы можем взять только верхнюю тарелку, и соответствует принципу LIFO (от англ. Last in, first out - Последним пришел, первым вышел). Однако в Python логику этой операции рас-
ширили для каждого типа данных.
Метод dict.popitem() не позволяет указать ключ элемента для удаления, а всегда удаляет последний добавленный элемент. При этом он возвращает не только значение, но и ключ удаляемого элемента в виде кортежа (ключ, значение) .
Коллекции 240
Метод	dict.popitem()
Описание	Возвращает и удаляет последнюю добавленную пару (ключ, значение) из словаря diet
Возвращаемое значение	Кортеж (ключ, значение)
До версии Python 3.7, метод diet .popitem() удалял случайную пару, но в более новых версиях языка он удаляет и возвращает последнюю добавленную пару, что опять же соответствует принципу LIFO и концепции стека: statues = {
"Будда Весеннего Храма": "Китай”,
"Рабочий и колхозница": "Россия", "Статуя Единства": ’Индия" } deleted_statue = statues.popitem() print(deleted_statue) # вывод: ('Статуя Единства', 'Индия')
print(statues)
#	Вывод: {‘Будда Весеннего Храма': 'Китай'j 'Рабочий и колхозница': 'Россия'}
Удаление всех элементов словаря
Также можно полностью очистить словарь, используя метод diet .clear().
Метод
Описание
Возвращаемое значение
dict.clear()
Удаляет все элементы словаря diet
None
Тогда словарь станет пустым:
movies = {
"Парк Юрского периода : "Стивен Спилберг", "Мартин Скорсезе": 'Славные парни’, "Кристофер Нолан": "Интерстеллар"
}
movies.с1еаг()
print(movies)
#	Вывоа: {}
Коллекции 241
Вложенные словари
Одноуровневая структура словаря может показаться недостаточной при обработке сложных структур данных, таких как информация о людях, компаниях или товарах. Поэтому на помощь приходят вложенные словари, позволяющие создавать иерархическую структуру данных.
Например, в библиотеке нам может понадобиться хранить информацию о книгах по жанрам и авторам: books = { "Научная фантастика": { "Кир Булычев": { "Сто лет тому вперед": { "Описание": "Приключения Алисы Селезнёвой', "Год издания": 1976, Ь "Любовь к трём цукербринам : {
Описание": "История любви и технологий будущего", "Год издания": 2013, ) } Ъ "Историческая литература": { “Борис Акунин": { "Коронация": { ’Описание": "Приключения Эраста Фандорина', "Год издания": 2000, } } } }
Чтобы обратиться к элементу вложенного словаря, нужно последовательно указывать ключи, начиная от внешнего уровня внутрь. Это очень похоже на индексацию во вложенных списках.
Например, если требуется получить данные о романе Бориса Акунина «Коронация», то запись будет выглядеть следующим образом: data = books["Историческая литература"]["Борис Акунин"]["Коронация"] print(data) # Вывод: {‘Описание': 'Приключения Эраста Фандорина ', ‘Год издания': 2000}
Примеры
Пример 1. Подсчёт частоты символов
Программа подсчитывает сколько раз каждый символ появляется в строке,
Коллекции 242
введённой пользователем:
text = input( ’Введите строку для подсчета частоты символов: )
counter = diet.fromkeys(text, 0) # Начальное значение счётчика - в
for cnar in text: counter[char] += 1
print(counter)
Вывод:
Введите строку для подсчёта частоты символов: Карабас-Барабас {’К': 1, 'а': 6, ’р’: 2, 'б': 2, 'с': 2,	1, 'Б': 1}
Пример 2. Авали) успеваемости студентов
Программа подсчитывает оценки студентов за экзамен.
grades = [5, 4, 3, 5, 4, 4, 3, 5, 4, 4, 2, 3, 5, 4, 3, 4, 4]
grade_counts = {} # Словарь для подсчета оценок for grade in grades:
#	Если в словаре есть ключ с оценкой grade, то берем его значение
#	Если в словаре нет ключа с оценкой grade, то берём 0
#	После этого счетчик увеличивается на 1
grade_counts[grade] = grade_counts.get(grade, 0) + 1
for grade, count in grade_counts.items():
print(f Оценка {grade}: {count} студентов’’)
Вывод:
Оценка 5: 4 студентов
Оценка 4: 8 студентов
Оценка 3: 4 студентов
Оценка 2: 1 студентов
Пример 3. Управление сессиями пользователей
Программа отслеживает активную сессию пользователя - это период времени, в течение которого пользователь взаимодействует с определенным приложением или сервисом, начиная с момента входа и заканчивая выходом или периодом бездействия:
actlve_sessions = {
"userl23": {"ip": "192.168.1.1", "last_activity": "10m ago"},
"admin": {"ip”: "10.0.0.5", "last_activity": "Im ago"}, "guest": {"ip": "172.16.0.10", "last_activity": "30m ago"} }
#	Закрываем сессию гостя
Коллекции 243
del active_sessions[ guest' J
#	Закрываем сессию пользователя и получаем её данные userl23_session_data = active_sessions.pcp( 'userl23') print(f Закрыта сессия userl23: {userl23_ses$ion_data} )
#	Если нужно закрыть последнюю сессию (например, для освобождения ресурсов) if active_sessions:
session_key, sessiori_data = active_sessions.popitem()
print(f Закрыта последняя сессия: {session_key}, {session_data} )
#	Закрываем все сессии при завершении работы сервера active_sessions.clear()
print(f Активные сессии: {active_sessions} )
Вывод:
Закрыта сессия userl23: {'ip': '192.168.1.1', 'last_activity‘: '10m ago'}
Закрыта последняя сессия: admin, {'ip': '10.0.0.5', 'last activity': 'Im ago'}
Активные сессии: {}
Ито1 и
J Ключи в словаре должны быть уникальными и пред славлены неизменяемым типом данных. Каждый ключ соответствует только одному значению.
J Функция dict() создаёт словарь из пар «ключ-значение», которые могут быть переданы как именованные аргументы ключ=значение или последовательность кортежей с парами (ключ, значение).
Функция zip() возвращает итератор кортежей, в котором каждый кортеж содержит элементы переданных коллекций, стоящие на одинаковых позициях. Это позволяет попарно объединить две коллекции: ключи и значения, и передать их функции dict() для создания словаря.
Для получения значения словаря в квадратных скобках указывается соответствующий ключ.
J Объединить два словаря в один новый можно через распаковку словарей в новый словарь конструкцией {**dictl, **dict2} или с помощью оператора |.
Оператор del удаляет ссылку на объект, например, на элемент словаря, списка или переменную.
Коллекции 244
	Таблица 17 - Методы словарей
Mei од	Описание
diet.fromkeys(iterable, Возвращает словарь, в котором ключи берутся из
value=None)	итерируемого объекта iterable, а все значения value одинаковы (по умолчанию None)
diet.get(key, default=None)	Возвращает значение из словаря diet по ключу key или значение по умолчанию default
diet.setdefault(key, default=None)	Возвращает значение из словаря diet по ключу key Если ключ не найден, добавляет в словарь элемент с ключом key и значением default
diet.update(other)	Дополняет исходный словарь diet парами «ключ-значение» из объекта other. Если добавляемый ключ существует, то перезаписывает его значение
diet.pop(key, default=None)	Удаляет из словаря diet элемент по ключу key и возвращает значение удаляемого элемента
dict.popitem()	Возвращает и удаляет последнюю добавленную пару (ключ, значение) из словаря diet
diet.clear()	Удаляет все элементы словаря diet
Задания для самопроверки
1.	Как функции diet() передаются пары «ключ-значение» для создания словаря?
2.	Дан словарь food = {‘‘Яблоки красные": "78 руб/кг", "Картофель свежий": "83 руб/кг", "Помидоры красные": "120 руб/кг"}. Выведите на экран стоимость 1 кг красных яблок и 1 кг жёлтых груш. Если в словаре нет данных о стоимости какого-то продукта, то выведите строку "Нет данных".
3.	Дан словарь capitals = {"Вологодская область": "Вологда", "Республика Татарстан": "Казань", "Республика Саха": "Якутск", "Германия": "Берлин"}. Измените столицу Вологодской области с "Вологда" на "Череповец" и удалите элемент с ключом "Германия". Полученный словарь выведите на экран.
4.	Дан список кортежей team = [("Иван", 25), ("Мария", 30), ("Петр”, 28) ]. Преобразуйте этот список в словарь, где ключами являются имена, а значениями - возраст. Полученный словарь выведите на экран.
5.	Дан список english_words = ["One", "Two", "Three", "Four", "Five"]. Создайте словарь, в котором ключи - числа от 1 до 5, а значения - перевод этих Коллекции 245
чисел на английский язык (список english words). Полученный словарь выведите на экран.
3.12 ГЕНЕРАТОРЫ КОЛЛЕКЦИЙ
Иногда возникает необходимость создать новую коллекцию на основе уже существующей. Представьте, у вас есть список чисел, и вам нужно получить новый список, содержащий только квадраты чётных чисел из этого списка.
Один из способов сделать это - перебрать все элементы исходного списка в цикле for и добавить в новый список квадраты тех чисел, которые делятся на 2 без остатка:
numbers = [1, 2, 3, 4, 5] squared_numbers = [] for n in numbers: if n % 2 == 0:
squared_numbers.append(n ** 2)
print(squared_numbers)
#	Вывод: [4, 16]
Этот код отлично справляется со своей задачей, но в Python существует более компактный способ создания списков, называемый генератором списка (или списочным выражением). Он позволяет создать новый список всего в одну строку кода.
Генераторы списков
Генератор списка - это мощный инструмент, который позволяет создавать новые списки на основе существующих итерируемых объектов (например, кортежей или других списков) с применением определенной логики. Общий синтаксис генератора списка выглядит так:
|выражение for элемент in коллекция if условие]
Давайте разберем каждый элемент этой конструкции:
о выражение - операция, которая применяется к каждому элементу из коллекции Результат этого выражения добавляется в новый список. Например, для получения квадрата числа выражение выглядит как п ** 2.
о элемент - временная переменная, которая последовательно принимает значение каждого элемента из коллекции. В нашем примере с числами это будет п.
Коллекции 246
о коллекция - итерируемый объект, который можно перебрать в цикле for. Это может быть список (например, numbers), кортеж, строка и даже результат работы функции range().
о условие (необязательно) - фильтр, согласно которому выбираются только те элементы из коллекции, которые удовлетворяют условию. Если условие истинно, то элемент обрабатывается и добавляется в новый список. Например. if п % 2 == 0 отбирает только чётные числа. Если условие отсутствует, обрабатываются все элементы.
Теперь давайте создадим список квадратов чётных чисел, используя генератор списка:
numbers = [1, 2, 3, 4, 5]
squarednumbers = [n ** 2 for n in numbers if n % 2 == 0] print(squared_numbers)
#	Вывод: (4, 16]
Как видите, код стал значительно короче. Он сразу показывает, что для каждого числа п из списка numoers мы хотим получить квадрат его числа п ** 2, если п является чётным, то есть if п % 2 == 0.
Особенности использования генераторов списков
Новый список может быть создан на основе любой из встроенных коллекций:
numbers tuple = (0, 5, 16, 0, 1, 0, 1)
no_zero_numbers = [n for n in numbers_tuple if n !- 0]
print (no_zero__numbers)
#	Вывод: [5, 16, 1, IJ
Здесь новый список no_zerojtumbers содержит все ненулевые элемента кортежа numbers_tuple.
Сами элементы коллекции, используемой в списочной выражении, могут быть представлены любым типом данных: строками и другими коллекциями:
passwords = {"qwerty", "ahif7821s", "12345", "IUkhsjdf87"}
good_passwords = [password for password in passwords if len(password) >= 8] print(good_passwords)
#	Вывод: ['ahi f7821 s', 'IUkhsjdf87‘]
Здесь в новом списке good_passwords содержатся только пароли, длиной больше 8 символов.
Для использования словаря в качестве коллекции в списочном выражении
Коллекции 247
следует вспомнить о методах dict.keys() и dict.values(). которые возвращают последовательности ключей и значений:
users_ages = { dragon99": 23, "dragcn20i0": 16, ’’Цветок Лотоса": 34}
users = [username for username in users_ages.keys() if username.starts-with("dragon")]
print(users)
#	вывод: ["dragon99", "dragon2010"]
Также мы можем одновременно перебирать последовательность пар (ключ, значение), полученную с помощью метода diet. items(). Например, если у нас есть словарь с пользователями и их возрастом, то мы можем извлечь имена только совершеннолетних пользователей:
users_ages = { dragon99": 23, "печенька": 16, Цветок Лотоса": 34} adult_users = [user for’ user, age in users_ages.items() if age >= 18] print(adult_users)
#	Вывод: ['dragon99‘, 'Цветок Лотоса']
Здесь переменная user принимает все значения ключей, а переменная age - значений. При этом, как в условии, так и в выражении мы можем использовать обе эти переменные.
Кроме того, источником данных для нового списка может служить последовательность чисел, полученная от функции range()
almost_squared_.numbers = [х ** 2 - 1 for х in range(5, 10)]
print(almost_squared_numbers)
#	Вывод: [24, 35, 48, 63, 8в]
Здесь функция range(5, 10) генерирует последовательность чисел от 5 до 9, а затем каждое число возводится в квадрат и уменьшается на единицу.
Вложенные генераторы списков
Генераторы списков могут быть вложенными, что позволяет создавать многомерные структуры данных:
Например, выведем на экран список, содержащий все строки таблицы умножения от 1 до 3:
result = [[i * j -for j in range(l, 4) ] for i in range(l, 4)] print(result)
#	Вывод: [[1, 2, 3], [2, 4, 6], [3, 6, 9]]
Здесь внешнее списочное выражение создает три списка, каждый из которых содержит результат умножения текущего числа i на числа от 1 до 3.
Коллекции 248
Генераторы множеств
По аналогии со списками, можно создавать и множества с помощью генераторов множеств Для этого используются фигурные скобки вместо квадратных:
numbers = [1, 2, 2, 3, 3]
numbers_set = {n for n in numbers} print(numbers_set)
#	Вывод: {lj 2, 3}
Генератор множеств игнорирует повторяющиеся элементы, что является ключевой особенностью множеств. Так в примере выше были удалены повторяющиеся числа 2 и 3.
Генераторы словарей
Для создания словарей также существует специальный синтаксис - генератор словаря. Он, как и множество, использует фигурные скобки, но внутри необходимо указывать пару ключ: значение, разделенных двоеточием: {выражение. для_ключа: выражение_для_значения for ключ, значение in коллекция if условие}
Очень похоже на списочное выражение, однако вместо одной переменной используется две: для ключей и для значений.
На коллекцию, из которой берутся данные для создания нового словаря, накладываются те же ограничения, что и на коллекцию, которая может быть передана функции diet() для создания словаря. Также нельзя забывать, что ключи словаря должны быть уникальными и неизменяемыми.
Например, создадим словарь из списка кортежей с парами (ключ, значение):
exam_results = [
( г рубель Михаил Александрович", 10В),
("Шагал Марк Захарович", 98),
( 'Рерих Николай Константинович", 92)
]
students_scores = {student: score for student, score in exam_results) point(students_sc ores)
#	Вывод: {'Врубель Михаил Александрович': 100, 'Шагал Марк Захарович': 98, 'Рерих Николай Константинович': 92}
Такую же последовательность кортежей можно создать из двух коллекций с помощью функции zip():
Коллекции 249
artists = ["Репин И.И.", “Айвазовский И,К. , "Левитан И.И."] arts = ("Бурлаки на Волге", "Черное море", "Золотая осень') artists_arts = {artist: art for artist, art in zip(artists, arts)} print(artistsarts)
#	Вывод: {'Репин И.И. ': 'Бурлаки на Волге', 'Айвазовский И.К. ': 'Черное море', 'Левитан И.И. ': 'Золотая осень'}
В каждом из созданных кортежей, из которых генерируется словарь, первый элемент взят из списка artists, а второй элемент - из кортежа arts. Поэтому словарь создаётся также как и в первом примере из списка кортежей.
Также словарь может быть сгенерирован на основе другого словаря:
studentgraoes = {
"Математика": [4, 5, 3, 5],
"Русский язык": [5, 4, а, 5],
'Информатика': [5, 4, 5, 4]
}
average_grades = {subject: sum(grades) / len(grades) for subject, grades in student_grades.items()}
print(average.grades)
#	Вывод: {'Математика': 4.25, 'Русский язык': 4.5, 'Информатика': 4.5}
Здесь в качестве значения нового словаря используется среднее арифметическое списка значений исходного словаря.
Генераторное выражение
Несмотря на то, что списки и кортежи во многом похожи и отличаются лишь изменяемостью, мы не можем использовать синтаксис списочного выражения для создания кортежа, просто указав круглые скобки вместо квадратных, как мы делали с фигурными скобками для множества и словаря:
numbers = [1, 2, 3, 4, 5]
squared_numbers = (n ** 2 for n in numbers if n % 2 == 0)
print(squared_numbers)
#	Вывод: generator object <genexpr> at 0x...>
Как вы видите, в таком случае print() выводит не кортеж, а объект generator Это значит, что числа 4 и 16 еще не были вычислены. Они будут вычислены только тогда, когда мы запросим их. например, в цикле for:
numbers = [1, 2, 3, 4, 5]
squared_numbers = (n ** 2 for n in numbers if n % 2 == 0) for sq_num in squared_numbers:
print(sq_num)
#	Вывод: 4
#	Вывод: 16
Коллекции 250
Такое выражение в круглых скобках называется генераторным выражением. Оно похоже на генератор списков по синтаксису, но вместо квадратных скобок используются круглые.
В отличие от генераторов списков, множеств или словарей, генераторное выражение не создает всю коллекцию в памяти сразу. Вместо этого оно возвращает специальный объект - генератор, который выдаёт элементы по одному, когда они запрашиваются. Его можно не только перебрать в цикле for, но и передать функциям, которые работают с итерируемыми объектами, например. sum(). min(). max(). all() или tuple().
Генераторные выражения гораздо более эффективны с точки зрения использования памяти, особенно при работе с большими наборами данных, так как-сразу вычисляют выражение и возвращают, ио не сохраняют результат.
Однако генератор можно использовать только один раз. После того, как все элементы были сгенерированы, он истощается и не может быть использован повторно. Например, если мы найдём максимальное значение генератора, то уже не сможем вычислить его сумму, так как он будет пустым:
squared_numbers = (п ** 2 for n in range(6) if n % 2 == 0) # 0, 4, 16 print(max(squared_numbers)) # вывод: 16
print(sum(squared_numbers))
#	Вывод: 0 (Так как генератор пуст после вызова тах())
По всегда можно преобразовать генератор в кортеж или другую коллекцию, если все-таки понадобится хранить все элементы:
squared_numbers = (п ** 2 for n in range(6) if п % 2 == 0) # 0, 4> 16
squared_nu(Obers_tuple = tup]e(squared_numbers)
print(squared_nunbers_tuple)
#	Вывод: (в, 4, 16)
Примеры
Пример 1. Транспонирование матрицы
В математике матрица-это прямоугольная таблица чисел, организованная в строки и столбцы. Программа транспонирует матрицу, то есть меняет местами строки и столбцы matrix = [ [1, 2, 3], [4, 5, 6], [7> 8, 9]
]
Коллекции 251
transposed = [[row[ij for row in matrix] for i in range(len(matrix[rJ))] for row in transposed:
print(row)
Здесь внешний генератор (for i in range(...)) перебирает индексы столбцов (от 0 до 2), а внутренний генератор (for row in matrix) последовательно извлекает i-й элемент из каждой строки (row). Таким образом, он создает новую строку из элементов старого столбца.
Вывод:
[1. 4, 7]
[2, 5, 8]
[3, 6, 9]
Пример 2. Перевод евро в рубли
Между народный интернет-магазин показывает цены в зависимости от страны пользователя. По умолчанию все цены указаны в рублях, поэтому программа преобразует их в соответствии с текущим курсом:
prices_eur = { Ноутбук": 800, "Телефон": 400, ’Планшет": 300}
excharge_rate = 91.7
prices_rub = {item: round(price * exchange_rate, 2) for item, price in prices_eur.items()}
print(prices_rub)
Здесь генератор словаря умножает каждое значение на куре и округляет результат до двух знаков после запятой.
Вывод:
Вывод: {'Ноутбук': 76400.0, 'Телефон': 38200.0, 'Планшет': 28650.0}
Пример 3. Фильтрация товаров в интернет-магазине
Программа отбирает товары с ценой выше 1 000 рублей и применяет к ним скидку 10%: products = [ {"Название": "Ноутбук', "Цена’: 45040}, {"Название : "Мышь", "Цена': 800}, {"Название": "Клавиатура', "Цена": 2500}, {"Название": "Наушники", ’Цена": 1500} ] discounted_products = [ {'Название": р["Назиание ], "Цена": р["Цена"] * 0.9}
Коллекции 252
tor p in products
if р["Цена' ] > 1003 ]
print(discountedproducts)
Вывод:
[{'Название': 'Клавиатура', 'Цена': 2250.0}, {'Название': 'Наушники', 'Цена': 1350.0}]
И101 и
J Генераторы коллекций позволяют создать новый список, множество или словарь на основе существующего итерируемого объекта.
J Генераторы коллекций могут быть вложенными.
J Конструкция [выражение for элемент in коллекция if условие] создаёт список.
J Условие не является обязательной частью списочного выражения
S Конструкция {выражение for элемент in коллекция if условие} создаёт множество.
J Конструкция {выражение_для_ключа: выражение_для_значения for ключ, значение in коллекция if условие} создаёт словарь.
Генераторное выражение в круглых скобках не создаёт кортеж. Конструкция (г-ыражение for элемент in коллекция if условие) создаёт генератор.
J Генератор выдаёт элементы по одному, когда они запрашиваются. Его можно не только перебирать в цикле for. но и передавать функциям, которые работают с итерируемыми объектами, например, sum(). min(). max(). all() или tuple().
J Генератор можно использовать только один раз. После того, как все элементы были сгенерированы, он не может быть использован повторно.
Задания для самопроверки
1.	Дан список brands = ["Apple", "Samsung", "Xiaomi", "LG"]. Создайте из нею новый список, содержащий только те слова, длина которых больше или равна 6 символам. Полученный список выведите на экран.
2.	Дан список numbers = [1, 2, 2, 3, 4, 4, 5]. Создайте из него множество, содержащее только чётные числа. Полученное множество выведите на
Коллекции 253
экран.
3.	Дан словарь prices = {"Роза красная": 180.5, "Хризантема жёлтая": 355.5, "Пион розовый": 325.5}. Создайте из него новый словарь, в котором цены увеличены на 10 %. Полученный словарь выведите на экран.
4.	Создайте матрицу 3 х 3 (3 строки и 3 столбца), заполненную нулями. Выведите полученную матрицу на экран, причем каждый вложенный список должен выводиться на новой С1роке.
5.	Запросите у пользователя целое число п > 1 и выведите на экран список
квадратов первых п натуральных чисел.
Пример входных данных Пример выходных данных
4	[1,	4,	9,	16]
6	[1,	4,	9,	16,	25,	36]
8	[1,	4,	9,	16,	25,	36,	49, 64]
Коллекции 254
1 лава 4
Функции
4.1 СОЗДАНИЕ ФУНКЦИЙ
Python предоставляет большой набор встроенных функций, некоторыми из которых мы уже пользовались, например, print() для вывода данных на экран или 1еп() для определения количества элементов в коллекции. Исходный код большинства встроенных функций написан на том же языке, что и сам интерпретатор Python. И если наиболее используемым интерпретатором является CPython, написанный на С, то и код многих функций написан на С. Но существует поддержка и других языков, например, интерпретатор Jython использует язык прог раммирования Java, a IronPython - С#.
Функции, написанные один раз, можно использовать бесконечное количество раз без необходимости повторного написания их кода. Это позволяет следовать одному из основополагающих принципов программирования DRY (от англ. Don’t Repeat Yourself - He повторяйтесь), который заключается в том. что следует избегать избыточного дублирования кода.
В случае встроенных функций, нам не нужно знать, как именно они реализованы. для того чтобы их использовать. У каждой функции есть имя. по которому её можно вызвать, опа может принимать определённые параметры и совершать определённые действия.
Вызов функции
Мы уже использовали множество функций, вызывая их по имени с помощью круглых скобок и передавая им аргументы.
Например, мы можем определить длину коллекции с помощью функции 1еп() и вывести её на экран, используя функцию print():
socks = [ 'Синий носок 1", "Синий носок 2 , "красный носок1’] print(len(socks)) # вывод: 3
Функция вызывается с помощью круглых скобок, так как без них она
Функции 255
является простым объектом, с которым можно выполнять различные действия, даже присваивать другим переменным:
say_something = print
Здесь переменной say_something присваивается значение объекта print, поэтому теперь её можно вызывать как функцию с помощью скобок:
say_something( Дождь, дождь, дождь,", end="\n||||||||||||||||||\п ) say_something( Поливай нашу рожь!") # Вывод: Дождь, дождь, дождь, # вывод: НИН IIIIIIII ПН # Вывод: Поливай нашу рожь!
Как и в функции print(), в новой функции say_something() можно использовать параметр end для изменения вывода в конце строки.
Параметры и аргументы
Когда мы говорим о функциях, часто упоминаются два термина: аргументы и параметры.
Параметры - это переменные, которые функция ожидает получить. Например, у функции print () есть параметр end, который определяет, чем заканчивается вывод.
Аргументы - это конкретные значения параметров, которые мы передаём функции. Например, в вызове print("Привет", end="!") строки "Привет" и "!" - это аргументы.
Совсем как в математике: у нас есть функция /'(%) = х ’ 4- 2, где х - это параметр функции, а его конкретное значение, например. 1 - это её аргумент.
Решение задачи с большим количеством повторений
Совсем скоро мы научимся писать свои собственные функции, а не только использовать существующие. Ио сначала давайте представим, что нам нужно написать программу, которая в конце четверти формирует список учеников по каждому классу, закончивших четверть на одни пятерки.
Исходные данные представляют собой словари по каждому классу, где ключом является ФИО ученика, а значением - вложенный словарь с оценками. В реальности у нас было бы больше двадцати словарей и сорока значений в каждом, но для примера ограничимся двумя классами и тремя учениками в каждом, а также оценками только по математике и русскому языку:
Функции 256
if 5 "A“ класс students_5A = {
"Циолковский Константин Эдуардович": {
"Математика": 5, 'русский язык": 5, Ъ "Третьяков Павел Михайлович": {
"Математика": 5,
"Русский язык": 5,
Ъ
"Достоевский Федор Михайлович': {
"Математика":
"Русский язык": 5, } }
# 5 "Б" класс
students_5B = {
"Королев Сергей Павлович": {
"Математика": 5,
"Русский язык": 5, Ъ
"Романов Пётр Алексеевич": {
"Математика": 4,
"Русский язык": 4, Ь
"Романова Екатерина Алексеевна": {
"Математика’: 4,
"Русский язык": 4, } }
Для каждого класса создадим пустые списки excellent_students_5A и ех-cellent_students_5B. в который будем добавлять ФИО отличников, перебирая словари в цикле:
excellent_students_5A = []
for student, grades in students_5A.items(): tor grade in grades.values():
if grade 1= 5: break else:
excellent_students_5A.append(student)
excellent_students_Sf = []
for student, grades in students_5B.iterns(): for grade in grades.values(): if grade != 5: break
else:
Функции 257
excellent_students_5B apoend(student)
Здесь оператор break прерывает внутренний цикл for, если оценка не равна 5, а блок else, в котором ФИО ученика добавляется в список, выполняется только в гом случае, если цикл не был прерван.
Выведем значения полученных списков на экран и убедимся в гом, что всё работает:
print(excellent_students_5A)
#	Вывод: ['Циолковский Константин Эдуардович', 'Третьяков Павел Михайлович'] print(excellent_students_5B)
#	Вывод: ['Королев Сергей Павлович']
Однако в школе обычно несколько десятков классов, тогда получается нам придётся столько раз продублировать блок кода с циклом?
Нет, если мы напишем собственную функцию.
Создание функции
Функции представляют собой именованный блок кода, который можно вызывать неограниченное количество раз в других частях кода. В общем виде функция записывается как:
def имя_функции(параметры):
тело-Функции return значение
Для определения функции используется ключевое слово def, за которым идёт имя функции.
Правила именования функций соответствуют правилам именования переменных, однако так как функция производит какое-то действие, то и начинать её имя рекомендуется с глагола, например, get (с англ. - получить), show (с англ. -показать) или open (с англ. - открыть).
Так как мы хотим создать функцию для получения списка отличников, то назовём её get_excellent_students.
Определившись с названием функции, создадим пустую функцию с помощью оператора-заполнителя pass:
def get_excellent_students(): pass
Оператор pass ничего не делает и обычно применяется в качестве заглушки. Его можно использовать не только при создании функций, но и.
Функции 258
например, в циклах или условиях. Такие пустые конструкции ничего не делают, но с их помощью можно заранее определить структуру программы.
В круглых скобках после имени функции указываются параметры функции - переменные, которые принимают значения, переданные при вызове функции. Наша функция get_excellent_students() должна принимать словарь данными по ученикам и их оценках, поэтому назовем его students:
def get_excellent_students(students): pass
При этом наличие параметров в функции вовсе не обязательно, а также они могут иметь значения по умолчанию.
Блок кода, который выполняет функция, называется телом функции. Для нас им является написанный ранее цикл, но вместо словаря с заданным классом используется словарь students, который функция принимает при вызове:
def get_excellent_students(students):
excellent_students = []
for student, grades in students.items():
for grade in grades.values():
if grade != 5:
break
else:
excelient_students.append(student)
Сейчас функция создаёт список отличников в классе, но не возвращает его. Поэтому если вызовем эту функцию, передав словарь students_5A, и выведем результат на экран, то увидим None:
excellent_students_5A = get_excellent_students(students_5A)
print(excellent_students_5A)
#	Вывод: None
Это связано с тем, что если функция не возвращает значение с помощью оператора return, то по умолчанию она возвращает None, поэтому переменной excellent_students_5A присваивается значение None.
Функцию, не возвращающую значение, можно использовать для совершения каких-либо действий, например, добавим в неё функцию print () для вывода списка отличников на экран:
def get_excellent_stuaents(students):
excellent_stuaents = []
for student, grades in students.items():
for grade in grades.values():
if grade != 5:
break
Функции 259
else:
excellent_students.append(student) print(excellent_students)
Тогда при вызове функции get_excellent_students() мы не получим сам список отличников, так как функция его не вернёт, но увидим его на экране:
get_excellent_students(students_5A)
#	вывод: ['Циолковский Константин Эдуардович', 'Третьяков Павел Михайлович']
Для того, чтобы функция возвращала значение, с которым можно будет работать дальше, используется оператор return, после которого указывается возвращаемое значение, например, переменная excellent_students:
def get_excellent_students(students): excellentstudents = [] for studentj grades in students.items(): for grade in grades.values(): if grade != 5: break else:
excellent_students.append(student) return excellent_students
Теперь для получения списка отличников в классе мы можем просто использовать функцию get_excellent_students (), передав ей словарь с учениками и их оценками:
excellent_students_5A = get_excellent_students(students_5A) print(excellent_students_5A)
#	Вывод: ['Циолковский Константин Эдуардович', 'Третьяков Павел Михайлович']
excellent_students_5b = get_excellent_students(students_5B) print(excellent_students_5B)
#	Вывод: [‘Королёв Сергей Павлович']
И больше не нужно писать цикл для каждого класса.
Особенности возвращения результата функции
Оператор return может возвращать не только переменную, но и вычисляемое выражение Например, создадим простую функцию get_circle_area(), которая принимает радиус круга и возвращает его площадь: def get_circle_area(radius): return 3.14 * radius ** 2
area = get_circle_area(3)
Функции 260
print(area)
# Вывод: 28.26
В Python, согласно стандарту РЕР8, функции и классы принято отделять от другого кода двумя пустыми строками. Внутри функции пустые строки используются для разделения логических блоков кода. Эти рекомендации относятся к стилю оформления кода и не влияют на работу программы.
Также оператор return останавливает выполнение флнкцин и любой код. написанный после него, не будет выполнен:
def get_circle_area_2(radius): area = 3.14 * radius ** 2 return area
print( Эта функция вычисляет площадь круга") # Не будет выведено
area2 = get_circle_area_2(l) p^int(area?) # Вывод: 3.14
Мы можем использовать это для реализации более сложной логики работы функции:
def get_circle_area_3(radius): if radius < 0:
return
area = 3.14 * radius ** 2 return area
агеаЗ = get_circle_area_3(-l) print(area)
# Вывод: None
Оператор return завершает функцию, поэтому нет необходимости заключать вычисление радиуса круга в блок else. Если функции будет передано отрицательное число, то она вернёт None, так как return без указания возвращаемого значения возвращает None
Функции 261
Функция может возвращать несколько значений, если перечислить их через запятую. Например, создадим функцию, которая считает не только площадь круга, но и его длину:
def get_circle_parameters(radius):
area = 3.14 * radius ** 2
length = 2 * 3.14 * radius return area, length
В таком случае используем множественное присваивание, и присвоим результат вызова згой функции двум новым переменным;
area, length = get_circle_parameters(3)
print(area, length)
#	Вывод: 28.26 18.84
Но если мы присвоим возвращаемые значения одной переменной, то при выводе её на экран увидим кортеж:
сirlce_parameters = get_circle_parameters(3)
print(cirlce_parameters)
#	Вывод: (28.26, 18.84)
To есть на самом деле оператор return всегда возвращает только одно значение: число, список, кортеж или другой тип данных, а значения, перечисленные через запятую после слова return создают кортеж.
Аннотации типов
Ранее мы уже говорили про аннотации типов в Python, однако они могут указывать не только типы переменных при их создании, но и типы параметров и возвращаемого значения И если параметры функции - это переменные, тип которых указывается через уже знакомую конструкцию имя: тип, то тип возвращаемого значения указывается перед двоеточием и после стрелки: -> тип
Название типа данных совпадает с названием функции, которая преобразует объект в этот тип данных. Например, тип данных list (список) и функция list() или тип данных str (строка) и функция str(), и так далее.
Коллекции обычно включают в себя данные одного или нескольких типов, которые могут указываться в квадратных скобках после названия типа переменной или возвращаемого значения, коллекция[тип1, тип2,Например, функция get_excellent_students() возвращает список строк, поэтому можем записать возвращаемое значение как list [str].
Однако для словарей в квадратных скобках через запятую указывается
Функции 262
сначала тип ключа, а затем тип значения: Р1с1[тип_ключа, тип_значения].
Давайте ещё раз посмотрим на словарь (diet), передаваемый функции get_excellent_students():
students_5A = {
'Циолковским Константин Эдуардович": {
"Математика": 5,
"Русский язык": 5,
Ь
"Третьякоа Павел Михайлович": {
"Математика": 5,
"Русский язык": 5,
Ь
"Достоевский Фёдор Михайлович': {
"Математика": 4, "Русский язык": 5, } }
Здесь ключами являются строки (str), однако значениями - другие словари (diet), в которых ключи — это строки (str), а значения - целые числа (int). Поэтому полный тип значения students можно записать как diet[str, diet [str, int]].
Теперь используем аннотации типов и укажем значения каких типов принимает и возвращает наша первая функция
defget_excellent_students(students: dictfstr, dict[str, int]]) -> list[str]: excellent_students = []
for student, grades in students.items(): for> grade in grades.values(): if grade != 5: break else:
excel!ent_students.append(student)
return excellent_students
Аннотации типов делают код функции более читаемым и понятным, особенно для других программистов или при повторном чтении собственного кода.
Также функция может принимать и возвращать значения разных типов. Например, созданная ранее функция get_circle_area _3() принимает целое число (int) или число с плавающей точкой (float), а возвращает None или число с плавающей точкой (float). В таком случае используется оператор |, который позволяет перечислить несколько типов данных:
def get_circle_area_3(radius: int | float) -> float | None: if radius < 0: return
Функции 263
area = 3.14 * radius *♦ 2 return area
Примеры
Пример 1. Проверка надежности пароля
Функция is_strong_password() принимает пароль password и проверяет, соответствует ли он минимальным требованиям безопасности: длина не менее 8 символов, наличие хотя бы одной буквы и одной цифры. Для этого она последовательно проверяет каждый символ пароля на принадлежность к буквам и цифрам. а также длину строки, возвращая True только если все условия выполнены: def is_strong_passwurd(password: str) -> bool:
..Проверяет, является ли пароль надежным: длина не менее 8 символов, содержит хотя бы одну букву и одну цифру.
Параметры:
password: Пароль.
возвращает.
True (пароль надёжный) или False (пароль не надёжный). II М II
#	Проверяем наличие хот» бы одной буквы в пароле has_letter = any(char.isalpha() for char in password)
#	Проверяем наличие хотя бы одной цифры в пароле has_digit = any(char.isdigit() for char in password)
#	Пароль считается надёжным, если:
#	- Длина >= 8 символов
#	- Содержит хотя бы одну букву
f	t	- Содержит хотя бы одну цифру
return len(password) >= 8 and has_letter and has_digit
print(is_strong_password("qwerty")) # Слишком короткий и нет цифр print(is_strong_password("Securel23")) # Удовлетворяет всем требованиям
Вывод:
False True
Пример 2. Счастливый билет
Билет, полученный в транспорте, считается счастливым, если сумма первых трёх цифр равняется сумме последних трёх цифр. Например, для билета
Функции 264
«123321» сумма 1 -i- 2 + 3 равна 3 + 2+1. Функция is_lucky_ticket() принимает номер билета ticket_number и возвращает True, если билет считается счастливым. Если номер билета некорректен или суммы цифр не совпадают, функция возвращает False:
def is_lucky_ticket(ticket_number: str) -> bool: .....Проверяет, является ли билет счастливым. Счастливым считается билет, где сумма первых трех цифр равна сумме последних трех цифр.
Параметры:
ticket_number: Номер билета.
Возвращает:
True (билет счастливый) или False (билет несчастливый).
fl II и
#	Проверим, что номер билета состоит ровно из 6 цифр if len(ticket_number) != 6 or not ticket_number.isdigit(): return False # Номер билета некорректен
#	Разделим номер билета на две части
first_half = ticket_number[:3] # Первые три цифры second_half = ticket_number[3:] # Последние три цифры
#	Считаем сумму цифр в первой половине sum_first = sum(int(digit) for digit in first_half)
#	Считаем сумму цифр во второй половине sum_second = sum(int(digit) for digit in second_half)
#	Бозвоащаем True, если суммы равны, иначе FaLse return sum_first == sumsecond
print(is_lucky_ticket( 123456 )) #(l + 2 + 3*4 + 5 + 6) print(is_lucky_ticket("123321 )) t(l+2+3=3+2+l)
Вывод:
False T rue
Пример 3. Шифр Цезаря
Шифр Цезаря - это шифр подстановки, названный в честь Юлия Цезаря, который по легенде использовал его для секретной переписки. Суть шифра заключается в том. что каждая буква в исходном тексте заменяется на другую букву, находящуюся в алфавите на фиксированном расстоянии от неё, которое называется ключом шифра. Например, если ключ шифра равен 3, то каждая
Функции 265
буква будет сдвинута на 3 позиции, поэтому буква «А» превратится в «Г», «Б» в «Д», и так далее. В конце алфавита счёт зацикливается, поэтому «Я» при том же сдвиге станет «В».
Функция caesar_cipher() шифрует текст с помощью шифра Цезаря, сдвигая каждую букву строки text на заданное количество позиций shift. При этом сохраняется регистр букв, а небуквенные символы остаются без изменений:
det caesar_cipher(text: str, shift: int) -> str: ......Шифрует русский текст шифром Цезаря.
Параметры:
text: Исходный текст для шифрования.
shift: Величина сдвига (ключ шифрования).
Возвращает: Зашифрованный текст.
II II II
lowercase = "абвгдеежзийклмнопрстуфхцчшщъыьэюя" uppercase = lowercase.upper() alphabet_length = len(lowercase) # 33 буквы
#	Приводим сдвиг к диапазону [0j 32] shift = shift % alphabet_lengtn
encryptedjtext = [] for char in text:
if char in lowercase + uppercase:
letters = lowercase if char in lowercase else uppercase
# Находим индекс символа old_index = letters.index(char) # Вычисляем новый индекс new_index = (old_index + shift) % alphabet-length # Добавляем зашифрованный символ encrypted_text.append(letters[new_index]) else:
# He буквенный символ (HanpuMepj пробел) оставляем без измене-ний encryptedtext.append(char)
return .join(encrypted_texc)
secret = caesar_cipher("У тебя всё получится! Ты молодец!’, 5) print(f"Зашифрованный текст: {secret}')
Здесь оператор остатка от деления (%) используется для того, чтобы реализовать циклический переход от буквы <<_Я>, к «А».
Функции 266
Вывод:
Зашифрованный текст: Ш чйед жцк фуршьнчцд! Ча суруииы!
Ито1 и
J Для вызова функции используются круглые скобки, без них функцию можно передать как объект другой функции или присвоить переменной.
J Параметры функции - это переменные, которые функция ожидает получить.
J Аргументы функции - это конкретные значения, которые переданы функции.
J Функция - это именованный блок кода, который выполняет определенную задачу и может быть вызван ио имени из других частей программы.
J Правила именования функций соответствуют правилам именования переменных, однако рекомендуется начинать её имя с mai 0.1а. указывающего на то, что функция делает.
J Тело функции - блок кода, который выполняет функция.
Оператор pass обозначает блок кода, который ничего не делает, то есть применяется в качестве заглушки. Он может применяться в функциях, а также условиях и циклах.
J Оператор return завершает выполнение функции и возвращает одно значение. Если значение не указано, то он возвращает None
J Аннотации типов могут использоваться для указания типов параметров и возвращаемого значения.
J Тип параметра указывает конструкция имя: тип, а тип возвращаемого значения- конструкция -> тип, написанная перед двоеточием, обозначающим начало блока с телом функции.
J Если функция принимает или возвращает значения разных типов, то они разделяются с помощью оператора |.
J Тип элементов коллекций указывается в квадратных скобках после названия типа коллекции. коллекция[тип1., тип2,.. ]. Однако тип элементов словаря указывается как <ЯсЦтип_ключа, тип_значения J
Задания для самопроверки
1.	Как связаны параметры и аргументы и функции?
Функции 267
2.	Напишите функцию greet(). которая ничего не возвращает, а выводит на экран строку "Добро пожаловать в мир функций! ". Затем вызовите эту функцию,
3.	Напишите функцию filter_positive(numbers), которая принимает список чисел numbers и возвращает новый список, содержащий только положительные числа (строго больше 0). Вызовите эту функцию для списка [1, -12, 31, -4, -5, 0, 24] и выведите результат на экран.
4.	Напишите функцию get_unique(numbers). которая принимает список чисел numbers и возвращает список без повторяющихся значений. Вызовите эту функцию для списка [100, 100, 200, 1, 100, -2, -2] и выведите результат на экран.
5.	Напишите функцию get_initials(full_name). которая принимает строку full_name с полным именем и возвращает инициалы в виде строки. Например, для строки "Репин Илья Ефимович" она должна вернуть строку "РЕК". Вызовите эту функцию для строки "Айвазовский Иван Константинович" и выведите результат на экран.
4.2 ПАРАМЕТРЫ И АРГУМЕНТЫ ФУНКЦИЙ
Параметры - это переменные, которые функция использует для получения значений, переданных ей при вызове. Они позволяют функции работать с данными, переданными извне, делая её более гибкой и универсальной. Значения, которые передаются функции при её вызове, называются аргументами.
Например, встроенная функция sum() возвращает сумму всех элементов коллекции. При этом коллекция должна быть передана как первый аргумент функции, а значение параметра start, определяющее начальное значение суммы, может быть не указано вовсе, передано по имени или как второй аргумент функции:
numbers = [1, 2, 3]
print(sum(numbers))
#	Вывод: 6
print(sum(numbers, start=10))
#	Вывод: lb
print(sum(numbers, 10))
#	Вывод: 16
Таким образом, значения параметров, то есть аргументы, могут быть переданы функции по позиции или по имени. Л сами параметры функции делятся па
Функции 268
обязательные и необязательные. Обязательные параметры должны быть переданы при каждом вызове функции, в то время как необязательные параметры могут быть опущены, и для них будет использовано значение по умолчанию.
Позиционные и именованные аргументы
Давайте напишем простую функцию create_superuser(), условно создающую пользователя по его имени и почте:
def create_user(username: str, email: str) -> None:
print(f"Создан пользователь: {username} c email: {email} )
Здесь у функции есть два обязательных параметра: username и email.
Если передать их значения в том же порядке, в котором они были определены в функции, то такие аргументы называются позиционными:
create_user("KingOfWesteros", "dragon777@example.ru")
#	Вывод: Создан пользователь: KingOfWesteros с email.: draqon777@exampLe.ru
Python сопоставляет первое значение "KingOfWesteros" с первым параметром username, а второе значение "dragon777@example.ru" со вторым параметром email.
Если аргументы передаются по имени соответствующего параметра, а не по его позиции в списке параметров, то такие аргументы называются именованными
create_user(email=’daenerys99@example.ru", username=”QueenOfWesterns )
#	Вывод: Создан пользователь: QueenOfWesteros с emaiL: daenerys99@exampLe.ru
При этом не имеет значения порядок следования, поэтому мы можем сначала передать значение параметра email, а затем - username Использование именованных аргументов делает код более читабельным, особенно если у функции много параметров.
Позиционные и именованные аргументы можно комбинировать. Однако существует важное правило: все позиционные аргументы должны идти перед именованными
create_user("PrinceOfDorne", email="snake69@example.ru")
#	Вывод: Создан пользователь: PrinceOfDorne с emaiL: snakeo9@exampLe.ru
Здесь первый аргумент "PrinceOfDorne" передан по позиции, а второй аргумент "snake69@example.ru" - по имени. Но наоборот делать нельзя, так как это приведёт к синтаксической ошибке:
create_user(email= snake69@example.ru', "PrinceO+Onrne")
Функции 269
#	Ошибка: SyntaxError: positional, argument foLLows keyword argument
Также можно указать, какие параметры будут приниматься только по имени. Для этого используется символ *. который отделяет позиционные аргументы от именованных.
Например, создадим функцию get_book(). которая принимает информацию о книги и выводит её на экран:
def get_bo'k(author: str, title: str, *, year: int, price; int) -> None: print(f"ABTop: {author}. Название: {title}. Год: {year} Цена: {price}")
Параметры year и price нельзя передать этой функции по позиции, так как после символа * параметры могут передаваться только по имени:
get_book( 'Алан Александр Милн", Винни-Пух и все-все-все , year=1969, price=99) # Вывод: Автор: Алан Александр Милн. Название: Винни-Пух и все-все-дсе. Год: 1969. Цена: 99
Необязательные параметры
Необятательным параметрам при создании функции присваивается значение по умолчанию, поэтому если при вызове функции для такого параметра не передано новое значение, то будет использовано значение по умолчанию.
При создании функции, параметры со значением по умолчанию должны следовать после обязательных параметров.
Например, создадим функцию filter_menu(), которая принимает статус наличия комплексных завтраков, обедов и ужинов в меню и выводит на экран список доступных из них: def filter_menu( is_breakfast: bool, is_lunch: bool=True, is_dinner: bool=True
) -> Norte:
menu = {''Завтрак": is_breakfast, "Обед": is_lunch, "Ужин": is_dinner} meals = [pos for pos, is_available in menu.items() if is_available] print(meals)
Здесь is_breakfast - обязательный параметр, и у него не задано значение по умолчанию, а параметры is_lunch и is_dinner - необязательные, поэтому у них есть значение по умолчанию и если мы не передадим им значение, то Python автоматически подставит True.
filtermenu(True)
#	Вывод: [ 'Завтрак'j ‘Обед’, 'Ужин']
Функции 27(1
Но мы можем изменить значение необязательного параметра, передав ему значение по позиции или по имени:
filter_menu(True, is_dinner=False)
#	Вывод: ['Завтрак'Обед']
filter_menu(True, False)
#	Вывод: [‘Завтрак’, 'Ужин']
filter_mpnu(True, True, False)
#	Вывод: ['Завтрак'j 'Ужин']
Изменяемый тип данных как значение но умолчанию
Параметры со значением по умолчанию очень полезны, но если это значение является изменяемым типом данных, то могут возникнуть проблемы.
Например, создадим функцию add_sterisk(), которая принимает один необязательный параметр 1st. по умолчанию представленный пустым списком, и добавляет звездочку в конец этого списка: def add_sterisk(lst: list[str]=[]) -> list[str]:
1 st.append() print(lst) return 1st
Если при вызове функции передавать ей какой-то список, то всё работает: add_sterisk([1, 2, 3]) # Вывод: fl, 2, 3, '*‘J aad_sterisk(['? , "I ,	])
it Вывод:	'!'j	'*']
Но при многократном вызове без переданных аргументов результат может показаться неожиданным:
aaa_sterisk()
# Вывод: ['*'] aad_sterisk() # Вывод:	'*']
add_sterisk() # Вывод: ['*',	'*’]
Это связано с тем, что пустой список 1st создаётся в памяти компьютера один раз при определении функции, и все следующие вызовы функции без переданных аргументов изменяют значение этого параметра, так как список являются изменяемым типом данных.
Чтобы функция работала так, как требуется, мы можем изменить значение по умолчанию с пустого списка на неизменяемый объект None'
Функции 271
def add_sterisk(lst: list[strj | Nocie=None) -> list[str]: if 1st is None:
1st = []
1st.append("*’) print(lst) return 1st
Теперь, сколько бы раз вы ни вызывали функцию, проблем не возникнет: add_sterisk() # Вывод: ['*'] add_sterisk() # Вывод: ['*'] add_steri sk() # Вывод: ['*']
Поэтому при определении значений по умолчанию для параметров функции следует использовать неизменяемые объекты
Произвольное количество аргументов
Иногда заранее неизвестно, сколько аргументов будет передано функции. Например, встроенная функция print() выводит на экран все переданные ей объекты. При создании собственной функции мы также можем указать, что она может принимать произвольное количество аргументов с помощью операторов * Ц * +
Произвольное количество позиционных аргументов
Оператор * перед именем параметра позволяет ему принимать любое количество позиционных аргументов, которые собираются в кортеж. Обычно такой параметр называют args (от англ, arguments - аргументы), но имя можно свободно изменять.
Например, напишем функцию sum_even_numbers(), которая принимает произвольное количество целых чисел и возвращает сумму только чётных из них. Внутри функции с параметром args можно работать как с обычным кортежем: def sum_even_numbers(*args) -> int: sumeven = 0 for number in args: if number % 2 == 0: sum_even += number print(sum_even) return sum_even
Функции 272
Мы можем передать этой функции любое количество чисел: sum_even_numbers(10, 11) Я Вывод: 10 sum_even_numbers(l, 2, 3, 4, 5) я Выв ос): 6
Как мы уже говорили, символ * позволяет отделить позиционные аргументы от именованных, но если в функции уже указан параметр *args, то этот символ не указывается, а все следующие аргументы в любом случае передаются по имени.
Например, напишем функцию sum_odd_numbers(), которая вычисляет сумму нечётных целых чисел, но при этом принимает стартовое значение суммы start и число stop, после которого вычисления заканчиваются:
def sum_udd_numbers(start: int, *args, stop: int) -> int: sum_odd = start for number in args:
if number == stop: break
if number % 2 == 1: sum_odd += number print(sum_odd) return sum_odd
При этом параметр stop может быть передан только по имени: sum_odd_numbers(0, 10, 11, 12, 13, 97, stop=13) Я Вывод: 11
Произвольное количество именованных аргументов
Оператор ** перед именем параметра позволяет функции принимать любое количество именованных аргументов, которые собираются в словарь Обычно такой параметр называют kwargs (от англ, keyword arguments - именованные аргументы), но имя можно свободно изменять.
Например, напишем функцию print_profile(), которая принимает произвольное количество именованных аргументов с данными о пользователе и выводит их на экран. Внутри функции с параметром kwargs можно работать как с обычным словарём:
def print_profile(**kwargs) -> None:
username = kwargs.get( username", "He определено ) print(f"Профиль пользователя: {username}. ) for key, value in kwargs.items(): if key != ‘username": print(f {key} {value}')
Функции 273
Ключами в словаре kwargs являются имена аргументов (например, username), а значениями - значения этих аргументов (например, "железнобокий"): рг1пГ_ргоГ11е(иьегпате="железн.)бокий", пате=”Бьёрн", age=30, city= ’Каттегат") # Вывод; Профиль пользователя: железнобокий: Я Вывод: пате: Бьёрн Я Вывод: аде: Зв Я Вывод: city: Каттегат
Комбинирование параметров в функции
В одной функции могут использоваться разные типы параметров. Как мы знаем, при вызове функции сначала передаются позиционные аргументы, а затем - именованные. Поэтому при создании функции параметры должны следовать в определённом порядке:
1.	Параметры, значения которых передаются по позиции.
2.	’args.
3.	Параметры, значения которых передаются по имени.
4.	**kwargs.
Давайте создадим функцию crGate_message() для создания сообщения: def create_message( recipient: str, text: str, sender: str=“CMCTeMa", *tags, **detaiis
) -> None:
print(f"OT: {sender} ) print(f"Кому: {recipient}' ) print(f"Текст: {text}')
if tags: print(f"Теги: {', '.join(tags)}’)
if details: print( Детали:") for key, value in details.items(): print(f - {key}: {value} )
Здесь у нас есть обязательные параметры с получателем recipient и текстом сообщения text, а также необязательный параметр с отправителем sender, так как по умолчанию сообщение приходит от системы. Кроме этого, функция принимает произвольное количество тегов и деталей, собираемых параметрами
Функции 274
*tags и **details.
В этой функции представлены все виды параметров, однако для её вызова достаточно передать значения только обязательных параметров recipient и text:
create_message(“Михаил Голицын"., "Привет!')
#	Вывод: От: Система
#	Вывод: Кому: Михаил Голицын
#	вывод: Текст: Привет!
Но также мы можем изменять значение по умолчанию и передавать произвольное количество позиционных и ключевых аргументов:
сreate_message("Екатерина Романова", "Срочно!1, "Григорий Орлов', "срочно', "внимание", due_date="3aBTpa", priority- высокий")
д Вывод: От: Григорий Орлов
#	Вывод: кому: Екатерина Романова
#	Вывод: Текст: Срочно!
#	Вывод: Теги: срочно, внимание
#	Вывод: Детали:
#	Вывод: - due_date: завтра
#	Вывод: - priority: высокий
create_message("Анна иоановна', "Отчет готов , "Эрнст Бирон", "финансы", "отчет")
#	Вывод: От: Эрнст Бирон
#	Вывод: Кому: Анна Иоановна
#	Вывод: Текст: Отчет готов
# Вывод: Теги: финансы, отчет
Примеры
Пример 1. Регистрация новою пользователя
13 системе администратор регистрирует новых пользователей с помощью функции register_user(), настраивая их права (администратор или обычный пользователь) и статус активности аккаунта:
def register_user( username: str, password: str, * J
is_admin: b3ol=False, is_active: bioi=True ) -> None:
...Регистрирует нового пользователя в системе.
Параметры: username: Имя пользователя.
Функции 275
password: Пароль.
is_admin: Указывает, является ли пользователь администратором. По умолчанию False.
isactive: Указь „ает, активен ли аккаунт. По умолчанию True. «I И II
print(f Регистрация пользователя \"{username}\"...")
print(f Статус: эктиген = {is_active}, администратор = {is_admin} ') # Здесь могла бы быть логика сохранения данных 6 базу
register_user( mr_potter", "passl234 ) т обычный пользователь
register_user( voldemort777", ”admin_pass', is_admin=True) # Администратор register_user("prof_snape’ , ”12345678', is_active=False) # Неактивный поль зобитель
Эта функция принимает обязательные позиционные параметры username с именем и password с паролем пользователя. После символа * определяются два необязательных параметра со статусом пользователя: является ли он администратором (is_admin) или активным пользователем (is_active) которые могут быть переданы только по имени. Они имеют значения по умолчанию False и I rue соответственно.
Вывод:
Регистрация пользователя "mr_potter"...
Статус: активен = True, администратор = False Регистрация пользователя “voldemort777"...
Статус: активен = True, администратор = True Регистрация пользователя "prof snape"...
Статус: активен = False, администратор = False
Пример 2. Форматирование заголовка отчета
Функция format_report_titleO генерирует форматированный заголовок для отчета, добавляя к нему имя автора (если оно известно) и произвольное количество тегов, чтобы облегчить поиск и категоризацию:
def format_report_title(title: str, author: 51г="неизвестен", *tags) -> None: ......Форматирует загол! ток отчета с автором и тегами.
Параметры:
title: Основной заголовок.
author: Автор отчета. По умолчанию "Неизвестен".
*tags: Произвольное количеств^ позицисиных тего! .
formatted_title = f {title.upper()} от {author} if tags:
Функции 276
formatted_title += f [Теги: {',	.join(tags)}]
print( -" * ]en(formatted_title))
print(formattedtitle)
print( * len(formatted_title))
Tormat_report_title("0T4eT по продажам", "Третьяков П. M. )
format_report_title("Маркетинговая кампания", "Потёмкин Г. А.", "срочно", "бюджет", "2025")
Эта функция принимает обязательный позиционный параметр title с заголовком отчёта, необязательный позиционный параметр author с автором отчёта и произвольное количество тегов *tags.
Вывод:
ОТЧЕТ ПО ПРОДАЖАМ от Третьяков П. М.
МАРКЕТИНГОВАЯ КАМПАНИЯ от Потемкин Г. А. [Теги: срочно, бюджет, 2025]
Пример 3. Запись данных в журнал событий
Функция легирования событий log_event() может принимать ие только основное сообщение, ио и любое количество дополнительных деталей о событии. например, идентификатор пользователя или код ошибки.
def log_event(message: str, **details) -> None: ......Записывает событие в журнал с дополнительными деталями.
Параметры:
message: Обязательное сообщение о событии.
♦♦details: Произвольное количество именованных аргументов с деталями.
II II II log_entry = f'Журнал событий: {message}" if details:
details_list = [f"{key}={value}” for key, value in details.items()] log_entry += f" ({', '.join(details_list)})“
print(logentry)
log_event( Пользователь i >шел в систему. )
log_event( Ошибка загрузки файла , user="admin", file_id=123, error_code=404) log_event( Операция успешно выполнена', module="API", duration_ms=150)
Эта функция принимает обязательный позиционный параметр message с
Функции 277
сообщением и произвольное количество деталей об этом сообщении **details.
Вывод:
Журнал событий: Пользователь вошел в систему.
Журнал событий: Ошибка загрузки файла (user=admin, file_id=123, er-ror_code=404)
Журнал событий: Операция успешно выполнена (module=API, duration_ms=150)
Итоги
Позиционные аргументы передаются в функцию в порядке их объявления, а именованные аргументы передаются по имени.
J Позиционные аргументы должны быть переданы до именованных.
J Необязательные параметры - это параметры, которые имеют значение по умолчанию, назначенное им при определении функции и которое используется, если при вызове функции это значение не было передано явно. Параметры *args и **kwargs позволяют передать в функцию произвольное количество аргументов. *args собирает позиционные аргументы в виде кортежа, a *’kwargs собирает именованные аргументы в виде словаря.
J Порядок параметров при создании функции: позиционные, *args, именованные, **kwargs.
Задания для самопроверки
1.	Чем позиционные аргументы отличаются от именованных?
2.	Напишите функцию calculate_total(price, quantity, discount=0), которая принимает два обязательных позиционных аргумента: цена товара price и количест во товара quantity, а также один необязательный именованный аргумент со скидкой discount (в процентах, значение по умолчанию равно нулю). Функция должна возвращать итоговую стоимость с учётом скидки. Вызовите эту функцию для price = 85. quantity » 2 и discount = 10. и выведите результат на экран.
3.	Объясните, почему при использовании изменяемых объектов (например, списков) в качестве значений по умолчанию для параметров могут возникнуть неожиданные результаты при многократном вызове функции.
4.	Для чего используются параметры *args и **kwargs? В каком порядке они должны следовать в определении функции?
5.	Напишите функцию show_profile(username, **details), которая
Функции 278
принимает обязательный позиционный аргумент с именем username и произвольное количество именованных аргументов **details. Функция должна выводить на экран имя пользователя и все значения словаря details через занятую. Вызовите функцию для username = "karen", occupation = "Журналист" и age=27.
4.3 ОБЛАСТИ ВИДИМОСТИ ПЕРЕМЕННЫХ
В Python, как и во многих других языках программирования, у каждой переменной есть своя область видимое! и, которая определяет, в какой части программы к ней можно получить доступ. То есть переменная, определенная в одном месте, может быть невидима в другом.
С областями видимости тесно связано понятие пространства имён. Оно представляет собой систему, которая связывает имена (например, переменных и функций) с соответствующими объектами в программе. Python использует иерархическую структуру пространств имен для управления областями видимости.
Правило LEGB
Для определения, где Python ищет имя переменной или другого объекта, используется правило LEGB. Это аббревиатура определяет порядок поиска имен Python ищет имя, начиная с самой внутренней (локальной) области и двигаясь наружу. Если имя найдено, поиск прекращается. Если оно не найдено ни в одной из областей, возникает исключение NameError.
Локальная область (L от англ. Local)
Это самая внутренняя область видимости. Она включает имена, определенные внутри текущей функции. Например, параметры функции или переменные, созданные внутри неё:
message = "Желаю необычайно хорошего настроения!"
def show_message() -> None:
message = 'Пусть день начнется с позитива! Пройдет легко и без надрыва!" print(message)
show_message()
Функции 279
#	Вывод: Пусть день начнется с позитива! Пройдет легко и без надрыва! print(message)
#	Вывод: Желаю необычайно хорошего настроения/
Здесь есть две переменные с одинаковым именем message, но они находятся в разных областях видимости. Внутри функции shou_message() создаётся новая, локальная переменная message с новым значением, которое и выводит на экран команда print (message). В тоже время, вызов print (message) вне функции обращается к глобальной переменной message, которая не была изменена.
Нелокальная область (Е от англ. Enclosing)
Если одна функция содержит другую, то локальные переменные внешней функции будут нелокальными переменными вложенной функции.
def greet() -> None:
greeting = "Приветствую! Чудесного, доброго и солнечного дня тебе!"
def smile() -> None:
print(greeting, end= )
emoj i = :)" print(emoji)
smile()
greet()
#	Вывод: Приветствую! Чудесного; доброго и солнечного дня тебе! :)
Здесь функция smile() вызывается внутри функции greet(), однако она не имеет собственной переменной greeting, поэтому эта вложенная функция ищет её в нелокальной области видимости - теле внешней функции greet().
Также обратите внимание, что функции Bnyipu функций отделяются друг от друга одной пустой строкой. Однако если внутренняя функция начинается сразу после внешней, то между ними не должно быть пустой строки.
Глобальная область (G оз англ. Global)
Это все имена, определенные в файле с кодом, но не внутри какой-либо функции. Переменные, созданные в глобальной области, называются глобальными переменными, и доступны для чтения и изменения из любой части файла, в котором они были объявлены.
Например, в нашем файле с кодом у нас есть глобальная переменная goodbye.
Функции 280
goodbye = "До встречи, берегите себя!"
Мы можем получить доступ к ней из любой функции в этом файле:
def say_goodbye() -> None: pr'int(goodbye)
say_goodbye()
it Вывод: До встречиj берегите себя!
Встроенная область (В от англ. Built-in)
Это самая внешняя область видимости, которая включает все встроенные в Python имена, такие как print, len, sum. str и другие. Они доступны в любой части программы.
Глобальные и локальные переменные
Мы уже упоминали о локальных и глобальных переменных, но давайте рассмотрим их более подробно.
Локальные переменные - это переменные, созданные внутри функции. Они существуют только в течение выполнения этой функции и доступны только внутри неё. Как только выполнение функции завершается, локальные переменные уничтожаются, и доступ к ним извне становится невозможен.
Глобальные переменные - это переменные, созданные на верхнем уровне программы, то есть вне каких-либо функций или классов. Они доступны для чтения и использования из любой части кода в этом файле, включая любые функции.
По умолчанию, функции могут только читать переменные из глобальной и нелокальной областей, но не изменять их. Если вы попытаетесь присвоить значение глобальной переменной внутри функции. Python создаст новую локальную переменную с тем же именем.
Чтобы явно указать Python, что вы хотите изменить именно глобальную переменную, а не создать новую локальную, используется ключевое слово global перед именем переменной:
number =10 # Глобальная переменная
def add_two() -> None:
it Используем глобальную переменную, а не создаем локальную global number
Функции 281
number = number + 2
print(f Внутри функции number = {number}")
add_two()
#	Вывод: Внутри функции number = 12
print(f"Снаружи функции number = {number}")
#	Вывод: Снаружи функции number = 12
Конструкция global number позволяет функции add_two() изменить значение глобальной переменной number.
Локальные и нелокальные переменные
Локальные переменные создаются внутри функции и видны только в её пределах. Если у нас есть вложенная функция, она также может создавать свои собственные локальные переменные, которые не будут влиять на переменные внешней функции.
Также мы можем использовать локальные переменные внешней функции внутри вложенной, тогда они называются её нелокальными переменными Они не являются ни локальными для вложенной функции, ни глобальными для всей программы, а принадлежат области видимости внешней функции.
По умолчанию такие переменные доступны только для чтения и для того, чтобы разрешить вложенной функции изменять значения переменных внешней функции используется ключевое слово nonlocal:
def modify_number() -> None: number = 10
def split_in_half() -> None: nonlocal number number /= 2
split_in_half() print(number)
modify_number()
#	Вывод: 5.0
Конструкция nonlocal number позволяет вложенной функции split_in_half() изменить значение переменной number из внешней функции modify_number()
Функции 282
Примеры
Пример 1. Учёт остатков па складе
Функция ship_product () осуществляет отправку товара со склада с учётом общего количества товаров, хранящегося в глобальной переменной total stock: total_stock = 5й0 # Общий запас товара (глобальная переменная)
def ship_product(quantity_shipped: int) -> None:
"""Отправляет товар со склада, уменьшая общее количество.
Параметры:
quantity_shipped: Количество товара для отправки.
II 11 II
global total_stock
if quantity_shipped <= total_stock: total_stock -= quantity_shipped print(f"Отправлено: {quantity_shipped} единиц. Остаток на складе: {total_stockj единиц')
else:
print( Недостаточно торара на складе для ггправки")
#	Показываем текущий запас
print(f"Текущий запас на складе: {totalstock} единиц")
#	Отправляем товары и наблюдаем за изменением глобальной переменной ship_product(150)
ship_product(300)
#	Показываем итоговый остаток
print(f"Итогивый остаток на складе: (total_stock) единиц")
Вывод:
Текущий запас на складе: 500 единиц
Отправлено: 150 единиц. Остаток на складе: 350 единиц
Отправлено: 300 единиц. Остаток на складе: 50 единиц
Итоговый остаток на складе: 50 единиц
Пример 2. Ипотечный калькулятор
Калькулятор рассчитывает ежемесячный платеж по ипотеке в зависимости от суммы кредита. Процентная ставка loan_rate может изменяться, но для удобства она определена во внешней функции calculate_mortgage(), а расчёты проводятся во вложенной функции calculate_monthly_payment():
def calculatejnortgage(loan_amount: float) -> None: “""Рассчитывает ежемесячный платеж по ипотеке.
Функции 283
Параметры:
loan_amount: Сумма кредита.
II II II
loan_rate = 0.07 # Переменная внешней функции (нелокальная для вложенной)
def calculate_mcnthly_payment(years: int) -> None: """Вложенная функция, которая рассчитывает платеж на ocHjee процентной ставки внешней функции.
Параметры:
years: Количество лет, на которое берётся кредит.
If II II
months = years * 12
monthly_rate = loan_rate / 12
payment = (loan_amount * monthly_rate) / (1 - (1 + monthly_rate) **
(-months))
print(f"Ежемесячный платеж на {years} лет: {payment:.2f} руб.")
# Рассчитываем ежемесячный платеж для 15 и 20 лет
calculate_mr>nthJy _payment( 15)
calculate_monchly_payment(2u)
# Запускаем калькулятор для суммы 5,000,000
calculate_mortgage(5_000_000)
Вывод:
Ежемесячный платеж на 15 лет: 44941.41 руб.
Ежемесячный платеж на 20 лет: 38764.95 руб.
Пример 3. У правление очками в игре
Функция start_game() симулирует запуск игры, начисление очков в которой осуществляется с помощью вложенной функции add_points(). Эта функция увеличивает значение переменной внешней функции score на переданное количество очков points. Для этого она использует ключевое слово nonlocal, которое позволяет функции изменять переменную score из внешней области видимости, а не создавать новую локальную переменную с этим же именем:
def start_game() -> None:
..Запускает игру и управляет счётом очков игрока. II II II
score = 0 # Переменная внешней функции (нелокальная для вложенной)
def add_points(points: int) -> None:
"""Вложенная функция, которая добавляет очки к счету игрока.
Функции 284
Параметры:
points: Количество очков, которые заработал игрок.
II II II nonlocal score score += points print(f Добавлено -{points} очков. Текущий счет: {score}")
# Симуляция игровых событий
print(f "Начал.-- игры. Счет: {score}") add_points(10) # Добавляем 10 очков aad_points(25) # Добавляем 25 очков aadpoints(5) # Добавляем 5 очков print(f Конец игры. Итоговый счет: {score}")
# Запускаем игру start_game()
Вывод:
Начало игры. Счет: 0
Добавлено 10 очков. Текущий счет: 10
Добавлено 25 очков. Текущий счет: 35
Добавлено 5 очков. Текущий счет: 40
Конец игры. Итоговый счет: 40
Итоги
J Область видимости переменной определяет, в какой части программы можно получить доступ к этой переменной.
J Пространства имён это система, сопоставляющая каждому имени соответствующий объект.
✓ Правило I.EGB определяет, где Python ищет имя объекта.
J Локальная область видимости - это область видимости внутри функции, которая включает в себя имена, определённые внутри неё.
J Нелокальная область видимости - это область видимости внешней функции для вложенной в неё другой функции.
J Глобальная область видимости - это область видимости всего файла с кодом.
Встроенная область видимости - это область видимости, которая включает в себя все встроенные в Python имена.
Локальные переменные - это переменные, созданные внутри функции.
Глобальные переменные - это переменные, созданные вне каких-либо функций, и доступные для чтения и изменения из любой части файла с Функции 285
кодом.
✓ Нелокальные переменные - это локальные переменные внешней функции для вложенной функции.
J По умолчанию, функции могут только читать переменные из глобальной и нелокальной областей, но не изменять их.
✓ Ключевое слово global позволяет функции изменять значение глобальной переменной.
Ключевое слово nonlocal позволяет внутренней функции изменять значение переменной внешней функции.
Задания для самоироьерки
I.	Что такое область видимости переменной и пространство имён в Python?
2.	Объясните, как работает правило LEGB при поиске переменной.
3.	Что делают ключевые слова global и nonlocal? В каких случаях их нужно использовать?
4.	Что будет выведено на экран в результате выполнения следующего кода? Объясните свой отвез.
name = “Николай"
def change_name() -> None:
name = "Па^ел"
change_name()
print(f"Император России: {name] Романог )
5.	Создайте глобальную переменную total_sum = 0 Напишите функцию add_to_total(number), которая принимает одно число number и добавляет его к total_sum. Вызовите эту функцию для number = 10. а затем для number = 5, после чего выведите на экран значение переменной total_sum.
4.4 РЕКУРСИВНЫЕ ФУНКЦИИ
Рекурсия это способ решения задачи, при котором функция вызывает сама себя, и такая функция называется рекурсивной.
Рекурсия позволяет, не используя цикл, разбить задачу на более мелкие, идентичные части и решать каждую из них с помощью того же самого алюритма.
Функции 286
Это похоже на ситуацию, когда для выполнения задания часть работы передаётся тому же исполнителю, но в более простом виде.
Рекурсия состоит из двух ключевых компонентов:
о Базовый случай - это условие, при котором функция прекращает вызывать саму себя. Он является точкой выхода и предотвращает бесконечные вызовы.
о Рекурсивный случай - это часть, где функция вызывает саму себя, но с измененными аргументами, чтобы каждый следующий вызов приближал функцию к базовому случаю.
Факториал как рекурсивная функция
Факториал - это математическая функция, которая для числа и возвращает произведение всех целых чисел от 1 до этого числа п включительно. Она обозначается восклицательным знаком после числа п и читается как и! (эн факториал).
Например, факториал числа 4 вычисляется как 4! = 1  2 • 3  4
Формально факториал для любого целого неотрицательного числа п можно записать как и! = п • (п - 1) • (п - 2) • ... • 2  1
При этом факториалы чисел 1 и 0 принято считать равным 1.
Факториал часто используют в комбинаторике и теории вероятности для подсчёта количества возможных перестановок. Однако нас больше интересует то, что вычисление факториала числа п - это классический пример рекурсивной функции.
Если мы запишем выражение для факториала как и! = и  (п - 1)!, то увидим. что определение факториала и! содержит в себе вызов самого себя (и - Г)! - это идеальный случай для рекурсии:
def factorial(n: int): -> int
#	Базовый случай: если п равно в или I, возвращаем 1 и прекращаем рекурсию if п  0 иг п •= 1:
return 1
#	Рекурсивный случай: п! = и * (л - 1)! return n * factorial^ - 1)
print (f"5! = {factorial^)}")
# Вывод: 5! = 120
Когда мы вызываем factorial(5), происходит следующая
Функции 287
последовательность вызовов:
I factorial(5) вызывает factorial^);
2.	factorial(4) вызывает factorial(3);
3.	factorial^) вызывает factorial^2);
4.	factorial(2) вызывает factorial(l);
5.	factorial(1) достигает базового случая и возвращает в factor}а!(2) число 1
Каждая функция возвращает выражение n * factorial(n - 1). Затем вызовы начинакп собираться в обратном порядке, вычисляя результат:
1.	factorial(2) возвращает в factorial(3) число 2. как результат 2*1;
2.	factorial(3) возвращает в factorial(4) число 6, как результат 3 * 2;
3.	factorial(4) возвращает в factorial(5) число 24, как результат 4 * 6;
4.	factorial(S) возвращает число 120. как результат 5 * 24
Преимущество рекурсивных функций заключается в простоте их определения и ясной логике. Теоретически все рекурсивные функции можно записать в виде циклов, но логика циклов может быть не так ясна, как логика рекурсии.
Переполнение стека и максимальная глубина рекурсии
При использовании рекурсивных функций необходимо соблюдать осторожность, чтобы не допустить переполнения стека вызовов Это происходит, когда функция вызывает саму себя слишком много раз, не достигая базового случая, что приводит к исчерпанию доступного размера стека вызовов.
Стек вызовов представляет собой специальную область памяти, которую операционная система использует для управления выполнением функций. Каждый раз, когда вызывается функция. Python создает для неё в стеке кадр стека, который содержит всю необходимую информацию: значения аргументов, локальные переменные и адрес, куда нужно вернуться после завершения работы функции.
Когда функция вызывает другую функцию, новый кадр вызова добавляется на вершину стека, и удаляется только после того, как функция завершила работу. Этот процесс работает по принципу, о котором мы упоминали, когда говорили о методе dict.pop() - «Последний пришел - первый вышел» или LIFO (от англ. Last In, First Out).
При использовании рекурсии каждый новый рекурсивный вызов добавляет
Функции 288
новый кадр в стек. Если рекурсия не имеет правильно определённого базового случая или если он никогда не достигается, функция будет вызывать саму себя до тех пор. пока стек вызовов не заполнится.
Максимальное количество вложенных вызовов рекурсивной функции называется максимальной глубиной рекурсии и в Python по умолчанию составляет около 1000 вызовов. Когда размер стека превышает его. Python принудительно прерывает выполнение программы и вызывает ошибку RecursionError. Это и есть переполнение стека.
Вы можете столкнуться с этим, если попробуете вычислить факториал 1000:
print(Г"1000! = {factorial(1000)}')
# Ошибка: RecursionError: maximum recursion depth exceeded
Однако факториал 999 должен быть вычислен: print(f"999! = lfactorial(999)} ') # Вывод: 999! = 40238726007... (очень большое число)
В большинстве случаев, если можно использовать простой цикл, лучше выбрать его. Рекурсию стоит применять, когда она делает код более читабельным и логически отражает структуру решаемой задачи.
Примеры
Пример 1. Ханойская башня
Ханойская башня - это популярная i оловоломка в виде трёх стержней, на один из которых надеты диски от большего к меньшему диаметру. Задача головоломки состоит в том. чтобы переместить все диски с одного стержня на другой за наименьшее число ходов. При этом необходимо соблюдать следующие правила:
о За один ход можно переместить только один диск.
о Диск можно класть только на больший диск или на пустой стержень.
о Нельзя класть больший диск на меньший.
Эту задачу удобно решить рекурсивно, так как её можно разбить на повторяющиеся однотипные подзадачи. Для того, чтобы перенести п дисков с первого стержня А на третий стержень С с помощью промежуточного стержня В, нужно выполнить три действия
1.	Переместить п~ 1 дисков со стержня А на стержень В.
Функции 289
2.	Переместить диск п со стержня А на стержень С.
3.	Переместить п - 1 дисков со стержня В на стержень С.
Рисунок 21. Ханойская башня с тремя дисками
При этом, чтобы переместить п - 1 дисков с первого стержня Л на третий стержень В. нужно выполнить те же самые действия для п - 2 дисков. А для перемещения п - 2 дисков должны быть выполнены те же действия для п - 3 дисков и гак далее.
Когда остался всего 1 диск, то есть и = 1, наступает базовый случай. Мы просто перекладываем этот диск со стержня А на стержень С, и рекурсия прекращается:
def hanoi(n: int, source: str, target: str, auxiliary: str) -> None: """Рекурсивно решает головоломку "Ханойская башня".
Параметры:
п: Количество дисков, которые нужно переложить, source: Стержень, с которого диски перекладываются, target: Стержень, на который диски перекладываются, auxiliary: Вспомогательный стержень.
•	I II II
#	Если остался один диск (п = 1), просто перемещает его if n == 1:
print(f"Переместить диск 1 с {source} на {target} ) return
#	Перекладывает п-1 дисков со стержня А на стержень В hanoi(n - 1, source, auxiliary, target)
Функции 290
#	Перекладываем оставшийся п-ый диск со стержня А на стержень С print(f Переместить диск {n} с {source} на {target} )
#	Перекладываем п-1 дисков со стержня В на стержень С hanoi(n - 1, auxiliary, target, source)
banoi(3, "A", "С", "B")
Здесь функция hanoi() вызывается для варианта головоломки, представленного на рисунке 21, то есть для грех дисков и башен А. В и С.
Вывод:
Переместить диск 1 с А на С Переместить диск 2 с А на В Переместить диск 1 с С на В Переместить диск 3 с А на С Переместить диск 1 с В на А Переместить диск 2 с в на С Переместить диск 1 с А на С
Пример 2. Поиск наибольшею общего делителя
Наибольший обший делитель (НОД) - это наибольшее натуральное число, на которое два или более данных натуральных числа делятся без остатка. Например, для чисел 12 и 18 НОД равен 6, поскольку это самое большое число, на которое делятся и 12 (12 = 6 • 2), и 18(18 = 6-3).
Наиболее эффективным способом нахождения НОД двух чисел является алгоритм Евклида. Он основан на том, что НОД двух чисел не изменится, если большее число заменить на остаток от деления большего на меньшее, то есть НОД(а. Ь) = НОД(Ь. а % Ь). Рекурсивная функция вызывает саму себя с обновленными значениями b и а % Ь, пока b не станет равным нулю, тогда наступает базовый случай, и функция возвращает а как НОД:
def gcd(a: int, b: int) -> int:
...Рекурсивно находит наибольший общий делитель двух чисел используя алгоритм Евклида.
Параметры:
а:	Первое число.
Ь:	Второе число.
возвращает:
Наибольший общий делитель (НОД) чисел а и Ь.
Функции 291
#	Базовый случай if b == 0: return a
#	Рекурсивный случай else:
return gcd(b, a % b)
print(f'HOfl чисел 48 и 18: {gcd(48, 18)}")
print(f‘НОД чисел 270 и 192: {gcd(270, 192)}* )
Покажем, как вычислялся НОД чисел 48 и 18:
о НОД(48, 18) - так как 18 не равно 0, вычисляем остаток от деления 48 на 18: 48 = 2-18+ 12.
Вызываем Н()Д( 18, 12).
о НОД( 18, 12) - так как 12 не равно 0, вычисляем остаток от деления 18 на 12: 18=1 • 12 + 6.
Вызываем НОД(12, 6).
о НОД( 12, 6) - так как 6 не равно 0, вычисляем остаток от деления 12 на 6: 12 = 2-6 + 0.
Вызываем НОД(6, 0).
о НОД(6, 0) - достигнут базовый случай. Второе число равно 0, поэтому НОД - это первое число, то есть 6.
Вывод:
НОД чисел 48 и 18: 6
НОД чисел 270 и 192: б
Итоги
J Рекурсивная функция - это функция, которая вызывает саму себя.
S Рекурсия состоит из базового случая - условия, при котором функция завершает вызывать себя, и рекурсивною случая - условия, когда функция вызывает саму себя для решения меныпей подзадачи.
Факториал - это математическая функция, которая для числа п возвращает произведение всех целых чисел от 1 до этого числа и включительно.
S Стек вызовов - это специальная область памяти, которую операционная система использует для управления выполнением функций.
J Кадр стека - блок данных, содержащий информацию о функции: значения аргументов, локальные переменные и адрес, куда нужно вернуться после
Функции 292
завершения работы функции.
✓ Работа рекурсивных функций опирается на стек вызовов, который работает по принципу L1FO или «Последний пришел - первый вышел».
J Максимальная глубина рекурсии - максимальное количество вложенных вызовов рекурсивной функции (около ]()()() по умолчанию).
J Ханойская башня - головоломка в виде трёх стержней, на один из которых надеты диски от большего к меньшему диаметру. Её цель состоит в том. чтобы переместить все диски с одного стержня на другой за наименьшее число ходов. Это ещё один классический пример задачи на рекурсию.
Задания для самопроверки
I.	Что такое рекурсия? Из каких двух ключевых компонентов она состоит?
2.	Объясните, почему для рекурсивной функции необходим базовый случай. Что произойдёт, если его не будет?
3.	Когда выполнение рекурсивной функции может привести к исключению RecursionError?
4.	Что будет выведено на экран в результате выполнения следующего кода? Объясните свой ответ.
def mystery(n: int) -> int:
if n == 0: return 1
else:
return 2 * mysteryfn - 1)
print(mystery(3))
5.	Напишите функцию powerCbasej exponent), которая принимает число base и целое неотрицательное число exponent, и возвращает результат возведения числа base в степень exponent Используйте рекурсию, а не оператор Вызовите эту функцию для base = 2 и exponent = 4, и выведите результат на экран.
4.5 ФУНКЦИИ ВЫСШЕГО ПОРЯДКА
В Python функции являются объектами, поэтому их можно присваивать переменным, возвращать из других функций и передавать в качестве аргументов другим функциям.
Функции 293
И если функция делает хотя бы одно из двух:
о принимает одну или несколько других функций в качестве аргументов: о возвращает функцию как результат своей работы.
То такая функция называется функцией высшего порядка.
Одной из таких функций является функция sorted(), с которой мы познакомились. когда говорили о сортировке списков. Ей можно передать функцию, которая будет применяться к каждому элементу итерируемого объекта перед сортировкой:
vegetables = ["Баклажан", "Щавель", "Тыква", "Сельдерей", "Спаржа"] print(sorted(vegetables, key=len)) # Сортировка по длине строки # Вывод: [' Тыква', ' Щавель'Спаржа', 'Баклажан', 'Сельдерей']
Поэтому функция sorted() является функцией высшего порядка. Кроме неё в Python представлены функции тар() и filter/).
Функция тарО
Функция тар() применяет заданную функцию к каждому элементу итерируемого объекта (например, списка, кортежа) и возвращает итератор с результатами.
Функция	map(function, *iterables) Возвращает итератор из элементов, полученных в резуль-
Описание	тате применения функции function к элементам одного или нескольких итерируемых объектов iterables • function - функция, которая будет применена к каждому
Параметры	элементу объекта iterable • *iterables - итерируемые объекты, к элементам которого применяется функция function
Возвращаемое значение	Итератор
Допустим, у нас есть список чисел, и мы хотим получить новый список, где каждое число возведено в квадрат, а затем из него вычтена единица. Для этого сначала напишем функцию custom_square(), которая совершает эту операцию над переданным ей числом:
def custom_square(number: int) -> int:
Функции 294
return number **2-1
Затем применим её к каждому элементу списка numbers с помощью функции тар():
numbers = [1, 2, 3, 4, 5]
squared_numbers = map(custom_square, numbers)
print(list(squared_numbers))
#	вывод: [0, 3, в, 15, 24]
Здесь функция шар() применяет функцию custom_square() к первому элементу списка п получает 0, затем применяет эту же функцию ко второму элементу и получает 3, и так далее. Она не изменяет исходную коллекцию, а возвращает итератор, который можно преобразовать в одну из коллекций, например, список.
Часто функция тар() находит применение в обработке данных, введённых пользователем. Так, функция input() всегда возвращает строку, поэтому при разбиении её на список методом str. split () каждый элемент этого списка тоже будет строкой. Для того, чтобы работать с каждым элементом как с числом, к нему следует применить функцию int():
numbers = map(int, input().split())
print (surn(nuinbers))
#	Ввод: 123 # Вывод: б
Здесь функция map() применяет к каждому элементу списка, полученного из строки, функцию int() и возвращает итератор, который мы можем передать любой функции, работающей с итерируемыми объектами, например, sum() для вычисления суммы элементов.
Также функции тар О можно передать не один, а несколько итерируемых объектов. Тогда она будет брать по одному элементу из каждого, поэтому функция должна принимать столько же аргументов, сколько итерируемых объектов было передано в тар().
Допустим, мы хотим поэлементно сложить три списка. Вместо того, чтобы перебирать эти списки в цикле, напишем функцию custom_plus(), которая складывает три переданные ей числа:
def custom._plus(numberl: int, number2: int, number3: int) -> int: return numberl + number2 + number3
И применим её к трём спискам:
numbersl = [1, 2, 3, 4, Ь]
numbers2 = [2, 3, 4, 5, Ь]
Функции 295
numbers} = [}, 4, 5, 6, 7, 8J
sum_numbers = map(custom_plus, numbersl, numbers2, numbers}) print(list(sum_numbers))
#	Вывод: [6, 9, 12, 15, 18J
Здесь функция map() берёт по одному элементу из каждого переданного списка и применяет к ним функцию custom_plus(), то есть складывает их. При этом функция тар () останавливается, когда исчерпывается самая короткая коллекция, поэтому последний элемент списка numbers3 не был обработан.
Функция flller()
Функция f ilter() создает итератор, который содержит только те элементы из итерируемого объекта, для которых заданная функция возвращает истинное значение True.
Функция	filter(function, iterable)
Возвращает итератор только из тех элементов итерируе-Описание	мото объекта iterable, для которых функция function вер-
нула True
* function - функция, которая будет применена к каждому
_	элементу объекта iterable
Параметры	_ _
• iterable - итерируемый ооъект, к элементам которого применяется функция function
Возвращаемое
Итератор значение
Функция filter(), как и тар(), принимает функцию и итерируемый объект. Однако она нс преобразует элементы, а отбирает только тс из них, для которых функция во звращает True.
Допустим, у нас есть список чисел, и мы хотим отфильтровать его, чтобы остались только чётные. Для этого нам нужно написать функцию is_even(), которая. если число чётное, возвращает True, а иначе - False:
def is_even(number: int) -> bool: return number % 2 == 0
Затем мы можем применить её к каждому элементу списка numbers, и в результате получим итератор только из тех элементов, для которых функция
Функции 296
вернула True:
numbers = [1, 2, 3, a, 5, 6, П, 8] even_numbers = filter(is_even, numbers) print(1ist(even_numbers))
#	Вывод: [2, 4, 6, 8]
Здесь функция filter() проверяет каждое число с помощью функции is_even(). Для числа 1 она возвращает False, поэтому оно пропускается в итоговой последовательности, а для числа 2 она возвращает True, поэтому оно включается в результат, и так далее.
Примеры
Пример 1. Конвертер иен
Цены на товары в международном интернет-магазине представлены в рублях и хранятся в списке. Функция шар() применяет функцию конвертации соп-vert_to_usd() к каждому элементу списка цен;
def convert_vo_usa(pric.e_in_rub: int | float) -> float: ......«инвертирует иену из рублей в доллары.
Параметры:
price_in_rub: Цена товара в рублях.
Возвращает. цена товара в долларах. II II II
exchange_rate =80.7 # Условный курс
return round(price_in_rub / exchange_rate, 2)
rubles = [1253, 2506, 5040, 750]
#	Конвертируем все цены с помощью тар() usd_prices = map(convert_to_usd, rubles)
print( Цены в рублях:", ^rubles)
print( Цены в долларах: *usd_prices)
Оператор * распаковывает элементы списка rubles и итератора usd_prices, передавая их в функцию print() как отдельные аргументы.
Вывод:
Цены в рублях: 1250 2500 5000 750
Цены в долларах: 15.49 30.98 61.96 9.29
Функции 297
Пример 2. Фильтрация списка транзакций
Список транзакций содержит положительные и отрицательные числа. Функция filter() позволяет получить итератор, содержащий только те транзакции, которые являются расходами (отрицательные числа). Для этого используется функция is_expense(). которая проверяет, является ли число отрицательным:
def is_expense(amount: float) -> bool: ......Проверяет, является ли транзакция расходом.
Параметры:
amount: Сумма транзакции.
□озвращает:
True (расход) или False (доход). «I »• II return amount < 0
transactions = [1000, -2500, 5200, -120, -1500, 800]
#	Фильтруем транзакции, оставляя только расходы expenses = filter(is_expense, transactions)
print( ^ce транзакции:", *transactions) print ('Только расходы:’, ’’’expenses)
Вывод:
Все транзакции: 1000 -2500 5200 -120 -1500 800
Только расходы: -2500 -120 -1500
Итоги
S Функция высшего порядка - это функция, которая принимает функции в качестве аргументов и/или возвращает функцию.
S Функция шар() применяет переданную функцию к каждому элементу итерируемого объекта и возвращает итератор с результатами.
S Функция filter() применяет переданную функцию к каждому элементу итерируемого объекта и возвращает итератор только с теми элементами, для которых функция вернула True.
Задания для самопроверки
1.	Что такое функции высшего порядка? Приведите примеры.
Функции 298
2.	Напишите функцию reduce_by_ten_percent(x), которая принимает число х и возвращает его. уменьшив на 10 %. Используйте функции тар() и ге-duce_by_ten_percent() для тою, чтобы уменьшить на 10 % каждое значение списка [105, 102, 24, 46, 50]. Выведите результат на экран.
3.	Чго будет выведено на экран в результате выполнения следующею кода? Объясните свой ответ.
def is_odd(number): return number % 2 != f)
numbers = [1, 2, 3, 4, 5, 6, 7] result = filter(is_odd, numbers) print(list(result))
4.	Ознакомьтесь с данным кодом и допишите функцию normalize() таким образом, чтобы она преобразовала фамилии в списке old_names к написанию, при котором первая буква заглавная, а остальные - строчные.
def normalize(name): pass
old_names = ["савельич", "ГРИНЁЕ , "ШВАБРин"]
new_names = list(map(nnrmalize, old_names)) print(new_names)
# Вывод: ['Савельич'j 'Гринёв', 'Швабрин']
5.	Напишите функцию is_short_word(text), которая принимает строку text и возвращает True, если её длина меньше или равна 4 символам, и False в противном случае. Используйте эту функцию в filter() и создайте из списка ["Киви", "Пляж", "Физика", "Кот", "Пр-’-граммирсвание" ] новый список, содержащий только короткие слова.
4.6 АНОНИМНЫЕ ФУНКЦИИ
Помимо обычных функций, которые мы создаём с помощью ключевого слова def, в Python существуют так называемые анонимные функции, которые не имеют имени и представляют собой небольшие однострочные функции. По-другому их называют лямбда-функциями (от англ, lambda functions) из-за их связи с лямбда-исчислением, разработанным американским логиком и математиком Алонзо Чёрчем в 1930-х годах.
Функции 299
В этой системе функция представляется в виде выражения, которое начинается с 1реческой буквы лямбда Л. Например, функция, которая удваивает число, в лямбда-исчислении записывается как Ах. 2  х, где:
о Л - указывает на начало определения функции;
о х - аргумент функции;
о 2  х - тело функции.
Многие языки программирования, включая Python или JavaScript, заимствовали эту концепцию для создания небольших одноразовых функций.
Создание анонимных функций
Анонимные функции создаются с помощью ключевого слова lambda, lambda параметры: выражение
Здесь:
о lambda - ключевое слово, указывающее на создание анонимной функции; о параметры - список параметров, который может быть пустым;
о выражение - одно единственное выражение, результат которого будет возвращён функцией.
Для примера давайте создадим обычную функцию для удвоения числа: def duuble(x: int) -> int: return x * 2
Анонимная функция, выполняющая это же действие, будет выглядеть следующим образом:
double_lambda = lambda х: х * 2
Анонимная функция не имеет собственного имени, однако она является объектом, поэтому её можно присвоить переменной. В качестве параметра здесь указана одна переменная х, а в качестве выражения - умножение этого параметра на число 2. Выражение в анонимной функции автоматически возвращается, поэтому нет необходимости использовать ключевое слово return.
Если мы вызовем эти две функции для одного числа, то увидим одинаковый результат:
print(dauble(5))
#	Вывод: 16
print(double_lambda(5))
#	Вывод: 10
Функции 300
Другими словами, анонимная функция является просто сокращённой формой для простых функций.
Однако анонимная функция может содержать только одно выражение То есть она не может содержать циклы, присваивания и другие конструкции, которые не являются выражениями.
Использование анонимных функций
Анонимные функции чаще всего используются в качестве аргументов для других функций, которые ожидают на вход другую функцию. Это особенно полезно при работе с функциями высшего порядка, такими как sorted(), map() и filter(), которые обрабатывают элементы коллекции на основе переданной им функции.
Сортировка словаря по ключу
Функция sorted() возвращает список независимо от типа переданного ей итерируемого объекта. Если передать ей словарь, то по умолчанию она вернёт отсортированный список его ключей. Однако метод diet.items() возвращает последовательность кортежей (ключ, значение), которую также можно отсортировать:
prices = {'Яблоки": 35, "Бананы": 48, "Лимоны': 31}
sorted_prices3 = sorted(prices.items())
print(sorted-prices 3)
#	Вывод: [('Бананы', 48), ('Лимоны', 31), ('Яблоки', 35)]
При этом такая последовательность сортируется по первому элементу каждого кортежа - ключу, а не по значению. Так как ключами в словаре prices являются строки, то и сортируются они в лексикографическом порядке.
Но если мы хотим изменить это поведение и сортировать словарь по ша-чениям, то нам поможет анонимная функция, принимающая кортеж и возвращающая второе его значение, которую мы можем передать параметру key: sorted_prices4 = sorted(prices.items(), key=lambda item: item[l]) print(sorted_prices4) # Вывод' [('Лимоны', 31), ('Яблоки', 35), ('Бананы', 48)]
Здесь lambda item: itemfl] - это анонимная функция, которая принимает кортеж item и возвращает его второй элемент, а функция sorted() использует результат этой функции для сортировки.
При необходимости отсортированный список можно передать функции
Функции 301
diet () и преобразовать его обратно в словарь:
new_prices = dict(sjrted_prices4)
print(new_prices)
#	Вывод: {'Лимоны': 31, 'Яблоки': 35, 'Бананы': 48}
Фильтрация чётных чисел
Анонимные функции могут использоваться для простой фильтрации элементов в коллекции. Например, оставим в списке только чётные числа:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers)) print(even_numbers)
#	Вывод: [2, 4, 6, 8, 10]
Здесь lambda x: x % 2 == 0 - это анонимная функция, которая возвращает True, если число чётное, и False в противном случае. Функция filter() использует это значение для включения или исключения элемента из результата.
Применение функции к каждому элементу списка
Также анонимная функция может применяться к каждому элементу коллекции с помощью функции тар(). Например, увеличим каждый элемент списка в 10 раз:
numbers = [1, 2, 3, 4, 5]
multiplied_by_ten = list(map(lambda x: x * 10, numbers))
print(multiplied_by_ten)
#	Вывод: [10, 20, 30, 40, 50]
Здесь анонимная функция lambda х: х * 10 применяется к каждому элементу списка numbers, а тар() возвращает итератор с результатами, который преобразуется в список.
Примеры
Пример 1. Сортировка результатов экзамена
Результаты экзаменов студентов в системе представляют собой список словарей. Функция sorted() сортирует их по набранным баллам: от большего к меньшему, но если у нескольких студентов одинаковый балл, то они сортируются по фамилиям в алфавитном порядке. Для этого она принимает анонимную функцию lambda student: (-student["Балл"], student [ "Имя"]), которая возвращает кортеж, поэтому Python сначала сравнивает первый элемент кортежа (балл), а затем, если они равны, второй элемент (имя):
Функции 302
students = [
{’’Имя'*: '‘Крузенштерн Инан", "Балл": 95},
{"Имя": "Лазарев Михаил", "Балл": 88},
{"Имя": "Никитин Афанасий", "Балл": 95},
{'Имя": ‘Дежнёв Семён , "Балл : 75},
{"Имя": "Головнин Василий", "Балл": 88}, ]
#	Сортировка по баллам (по убывании)), затем по имени (по алфавиту) sorted_students = sorted(students, key=lambda student: (-student}'Балл"], student} ‘Имя"]))
for student in sorted_students:
print(f {student}'Имя']}: {student}'Балл']} баллов )
Баллы сортируются по убыванию, так как перед значением student ["Балл"] указан знак минус (-). Такой способ изменения направления сортировки работает только для чисел.
Вывод:
-	Крузенштерн Иван: 95 баллов
-	Никитин Афанасий: 95 баллов
-	Головнин Василий: 88 баллов
-	Лазарев Михаил: 88 баллеь
-	Дежнёв Семён: 75 баллов
Пример 2. Расчёт стоимости товара с учётом НДС
Конечная стоимость товара обычно включает в себя НДС (налог на добавленную стоимость). Цены товаров хранятся в списке, а по общему правилу НДС исчисляется по ставке 20 %, поэтому к каждому элементу списка нужно применить одну и ту же формулу с помощью функции тар() и анонимной функции lambda price: price * 1.2, которая увеличивает цену price на 20 %:
prices = [1000, 2500, 500, 1200]
#	Рассчитываем цены с налогом prices_with_tax = list(map(lambda price: price * 1.2, prices))
print( Исходные цены: , prices)
print( Цены с учётом НДС: , prices_with_tax)
Вывод:
Исходные цены: [1000, 2500, 500, 1200]
Цены с учетом НДС: [1200.0, 3000.0, 600.0, 1440.0]
Функции 303
Пример 3. Фильтрации адресов электронной почты
Перед рассылкой система (весьма упрощённо) проверяет список всех почтовых адресов и с помощью функции filter() оставляет в них только те. которые содержат символы и "." (точка). Для этого она принимает анонимную функцию lambda email:	in email and in email, которая возвращает
True, если нужные символы содержатся в строке email и False в противном случае:
emails = [
"iron_man@exampl е. ru",
“no-data"j
"None",
"invincible_thor@domain.net", "cap=am@ , "not_hulk@tes<"
]
#	Фильтруем список, оставляя только валидные адреса valid_emails = filter(lambda email:	in email and in email, emails)
print( Исходный список почтовых адресов:”, *emails)
print( Проверенные адреса:', *valid_emaiIs)
Вывод:
Исходный список почтовых адресов: iron_man@example.ru no-data None invinci-ble_thor@domain.net cap=am@ not_hulk@test
Проверенные адреса: iron_man@example.ru invincible_thor@domain.net
Итоги
J Конструкция lambda параметры: выражение создаёт анонимную функцию - небольшую однострочную функцию без имени.
Анонимные функции чаще всего используются в качестве аргументов для других функций, которые ожидают на вход другую функцию. Например, sorted(), filter() ишар().
Задания для самопроверки
1	Что такое анонимная функция? Как она создаётся?
2.	Что будет выведено на экран в результате выполнения следующего кода? Объясните свой ответ.
my_lambda_function = lamoda a, b: а + b
print(my_lambda_function(5, 10))
Функции 304
3.	Напишите анонимную функцию, которая принимает три числа и возвращает их произведение. Присвойте эту функцию переменной product_lambda и вызовите её с гремя любыми числами.
4.	Дан список кортежей students = [("Менделеев Иван", 4.5), ("Кюри Мария", 5.0), ("Кюри Пьер", 3.8)], где каждый кортеж содержит имя школьника и его средний балл. Используйте анонимную функцию и функцию sorted(), и отсортируйте список students по оценкам в порядке убывания (от самой высокой к самой низкой). Отсортированный список выведите на экран.
5.	Дан список numbers = [1, 2, 3, 4, 5] Используйте анонимную функцию и функцию тар(), и увеличьте каждое число из списка numbers на 10. Преобразуйте полученный итератор в список и выведите его на экран.
6.	Дан список zoo = ["слон", "жираф", "крокодил", "зебра", "коала"]. Используйте анонимную функцию и функцию f ilter(). и создайте из списка zoo новый список, содержащий только слова, содержащие символ "к". Преобразуйте полученный итератор в список и выведите его на экран.
4.7 ИТЕРАТОРЫ И ГЕНЕРАТОРЫ
В Python работа с последовательностями данных часто строится на двух ключевых понятиях: итераторы и генераторы. Они позволяют эффективно обрабатывать большие объемы данных, избегая необходимости хранить всю последовательность в памяти.
Ит ера торы
Мы уже не единожды работали с итераторами, например, когда перебирали элементы коллекций в цикле for. Он неявно превращает итерируемый объект в итератор, обеспечивающий последовательный доступ к его элементам. Но также мы можем вручную преобразовать итерируемый объект в итератор и последовательно получить каждый его элемент с помощью функций iter() и next().
Функция iter() принимает итерируемый объект (например, список, кортеж или строку) и возвращает его итератор. Он не является копией переданной коллекции, а просто указывает на то, где в данный момент находится процесс итерации.
Функции 305
Функция	iter(collection)
Описание	Возвращает итератор из элементов коллекции collection
Параме1ры	• collection - коллекция, из элементов которой получается итератор
Возвращаемое значение	Итератор
Функция next() принимает итератор и возвращает следующий элемент из его последовательности.
Функция
Описание
Параметры
Возвращаемое значение
next(iterator, default)
Возвращает следующий элемент итератора iterator
•	iterator - итератор, следующий элемент которого требуется получить
Необязательные параметры:
•	default - значение, которое будет возвращено, когда элементы итератора закончились. По умолчанию не задано и вызывается исключение Stopiteration
Следующий элемент итератора
Давайте создадим итератор из списка, который переберём с помощью функции next(). При каждом вызове этой функции итератор сдвигается на один элемент вперед:
sweets = iter(["Шоколад"л "Сладкая вата"]) print(next(sweets)) # Вывод: шоколад print(next(sweets)) # Вывод: Сладкая вата print(next(sweets)) # Ошибка: Stopiteration
Когда элементы в итераторе заканчиваются, функция next() вызывает исключение Stopiteration. Это исключение не является ошибкой в обычном смысле; оно просто сигнализирует Python, что итерация завершена. Цикл for
Функции 306
автоматически обрабатывает это исключение, чтобы корректно завершить свою работу.
Также избежать вызов исключения позволяет параметр default, значение которого будет возвращаться, если в итераторе закончились элементы:
sweets = iter(["Шоколад", "Сладкая вата"]) print(next(sweets, "Сладости закончились )) # Вывод: Шоколад
print(next(sweets, "Сладости закончились )) # Вывод: Сладкая вата
print(next(sheets, "Сладости закончились ))
#	Вывод: Сладости закончились
Вместе функции iter() и next() формируют основу для ручного прохождения по коллекции. С их помощью мы даже можем реализовать цикл for через цикл while:
books = iter([ Метаморфозы", "Государь", "1984"]) while True:
try:
book = next(books)
print(book)
except Stopiteration: break
#	Вывод: Метаморфозы
#	Вывод: Государь
#	Вывод: 1984
Генераторы
Мы уже создавали генераторы с помощью генераторных выражений:
numbers = (п ** 2 for n in range(l, 100С000000))
На создание списка на основе этого выражения потребовалось бы значительное количество времени и памяти. Однако генераторы, как и итераторы, не хранят всю последовательность в памяти, а генерируют значения по одному, что делает их идеальными для обработки больших файлов или потоков данных.
Так как генератор является особым видом итератора, каждый его элемент можно последовательно получить с помощью функции next():
numbers = (n ** 2 for n in range(l, 100000b))
print(next(numbers))
#	Вывод: 1
print(next(numbers))
#	Вывод: 4
print(next(numbers))
Функции 307
# Вывод: 9
Хотя более эффективно использовать цикл -For, так как тогда не требуется вручную обрабатывать исключение Stopiteration:
numbers = (n ** 2 for n in range(l, 1000000000)) for n in numbers:
if n >= 100: print(n) if n == 144: break # Вывод: 100 # Вывод: 121 # Вывод: 144
Так мы можем получить первые элементы последовательности из миллиарда элементов без необходимости её создания, то есть сэкономив время и память.
Функции-генераторы
Функции-генераторы похожи на обычные функции, однако вместо ключевого return они используют ключевое слово yield. Это связано с тем, что обычные вызовы функций возвращают результаты напрямую, а вызов функции-генератора возвращает объект-генератор.
Оператор yield - это то. что отличает генератор от обычной функции.
Когда Python встречает yield, он делает следующее:
о Приостанавливает выполнение функции.
о Возвращает значение, указанное после yield.
о Запоминает все локальные переменные и текущую позицию в коде.
Например, напишем простую функцию-генератор command_generator()-def command_generator(): yield 'На старт!" yield "Внимание!" yield "Марш!"
Вызов функции-генератора не возвращает значение напрямую, а создаёт объект-генератор:
commands = command_generator() print(commands)
#	Вывод: (generator object comfnand_generator at 0x...>
Но при каждом вызове генератора, например, в цикле for или с помощью
Функции 308
next(), выполнение функции возобновляется с того же места, где оно было приостановлено, до следующего yield: print(next(commands)) # Вывод- На старт!
print(next(commands)) # Вывод: Внимание!
print(next(commands)) # Вывод: Марш!
Это позволяет создавать последовательности значений по мере необходимости.
Вызов функции-генератора создаёт объект-генератор. Многократный вызов функции-генератора создаёт несколько независимых генераторов. Например, здесь функция всегда будет возвращать "На старт!
print (next(conimand generator())) # Вывод: На старт!
print (next (co,Timand_generator()))
#	Вывод: На старт!
print(next(command_generator())) # Вывод: На старт!
Причина этого заключается в том, что три вызова функции command_gener-ator() создают три совершенно независимых генератора. Поэтому вызов функции next() для каждого возвращает его первое значение.
Генератор для чисел Фибоначчи
Последовательность Фибоначчи - это бесконечная последовательность чисел, где каждое следующее число является суммой двух предыдущих. Генераторы идеально подходят для таких задач, так как позволяют генерировать числа по мере необходимости, не создавая длинную последовательность сразу: def fibonacci_generator(): a, b = 0, 1 while True: yield a ал b = b, a + b
Здесь функция fibonacci_generator() определена как генератор и создаёт две переменные а и b со значениями 0 и 1 - двумя первыми числами Фибоначчи.
В бесконечном цикле while True функция возвращает текущее значение переменной а и приостанавливает выполнение функции, запоминая ее состояние.
Функции 309
При каждой следующей итерации по генератору функция возобновляет выполнение с того места и вычисляет следующее число Фибоначчи, обновляя значения а и Ь: а становится b. а b становится суммой предыдущих значений а и Ь.
Например, выведем на экран первые десять чисел Фибоначчи:
fib..gen = fibonaccigeneratorO
for _ in range(lB): print(next(fib_gen), end=" )
#	Вывод: 0 1 1 2 3 5 8 13 21 34
Здесь функция-генератор fibonacci_generator() не создаёт и не хранит весь список чисел, а генерирует их по одному при каждой итерации в цикле for.
В Python подчеркивание (_) может использоваться в качестве имени неиспользуемой переменной для обозначения того, чго значение переменной не понадобится в дальнейшем. Но это не правило, а соглашение об именовании, которое не влияет на работу про-траммы, но позволяет не загромождать пространство имён лишними переменными.
Далее мы можем воспользоваться этим же генератором и получить следующие числа Фибоначчи, например, с помощью функции next():
print(f \пОдиннадцатое числи Фибоначчи: {next(fib_gen)}“)
#	вывод: Одиннадцатое число Фибоначчи. 55
print(f Двенадцатое и тринадцатое числа Фибоначчи: {next(fib ,_gen)} и {next(fibgen)}")
#	Вывод: Двенадцатое и тринадцатое числа Фибоначчи: 89 и 144
Таким образом, в ходе программы мы можем снова и снова получать новые значения из генератора. И если генераторы с конечной последовательностью могут быть исчерпаны, то бесконечные генераторы не исчерпываются, по требуют осторожного использования.
Примеры
Пример 1. Управление списком задач
Задачи из списка в системе управления проектами выполняются по одной
Функции 310
с явным указанием, когда нужно перейти к следующей,
tasks = [
"Позвонить Сталину И. В.",
"Забрать посылку',
"Ответить на письма , "Написать отчёт"
]
#	Создаем итератор из списка задач task_iterator = iter(tasks)
#	Выполняем задачи по одной print( Выполняем следующие задачи:") print(f" - {next(task_iterator)J ) print(f'' - {next(task_iterator)J")
#	Проверяем, есть ли еще задачи print(f"Осталось задач: {len(list(task_iterator))}")
Вывод:
Выполняем следующие задачи: - Позвонить Сталину И. В. - Забрать посылку
Осталось задач: 2
Пример 2. Генератор уникальных идентификаторов
Для новых пользователей система с помощью генератора id_generator() последовательно назначает уникальные идентификаторы. Это позволяет избежать необходимости создавать и хранить список всех возможных 1D в памяти: def id_generator():
..Генерирует последовательность уникальных ID.
lastid = 0
while True: last_id += 1 yield last_id
#	Создаем объект-генератор для ID userids = id_generator()
#	Получаем новые ID no мере необходимости print(f Новый пользеьатель зарегистрирован с print (f'HoBbin пользователь зарегистрирован с print(f Новый пользователь зарегистрирован с
ID: {next(user_ids)}')
ID: {next(user_ids)}")
ID: {next(user_ids)} )
Функции 311
Вывод:
Новый пользователь зарегистрирован с ID: 1
Новый пользователь зарегистрирован с ID: 2
Новый пользователь зарегистрирован с ID: 3
Пример 3. Пагинация на сайте
Пагинация на сайте - это метод разбиения большого объема информации на несколько страниц, делая сайт более удобным. Генератор paginate() имитирует вывод результатов поиска и на каждой итерации возвращает срез большого списка данных data, то есть разбивает его на меньшие списки (страницы) по не более чем page_size элементов в каждом:
def paginate(data: list, page_size: int): .......Генерирует страницы данных из большого списка.
Параметры:
data: Список данных.
page_size: Количество элементов на странице.
Yields:
Список элементов на следующей странице. II II II
for i in range(0, len(data), page_size): yield data[i:i + page_size]
all_results = ]ist(range(l, 46)) # 45 результатов
#	Разбиваем результаты на страницы по 1в элементов pages = paginate(all_results, 10)
#	Проходим по страницам и выводим их for i, page in enumerate(pages):
print(f Страница {i+1}: {page} )
Вывод:
Страница	1:	[1, 2, 3, 4,	5, 6, 7,	8,	9, 10]		
Страница	2:	[11, 12, 13,	14, 15,	16,	17, 18,	19,	20]
Страница	3:	[21, 22, 23,	24, 25,	26,	27, 28,	29,	30]
Страница	4:	[31, 32, 33,	34, 35,	36,	37, 38,	39,	40]
Страница	5:	[41, 42, 43,	44, 45]				
Итоги
S Функция iter() преобразует итерируемый объект в итератор.
J Функция next() возвращает следующий элемент итератора.
Функции 312
J Генераторы не хранят всю последовательность в памяти, а генерируют зна-
чения по одному.
Функции-генераторы похожи на обычные функции, однако вместо ключевого return они используют ключевое слово yield
S Оператор yield приостанавливает выполнение функции, возвращает значение, указанное после yield, и запоминает все локальные переменные и текущую позицию в коде.
J При каждом вызове генератора, например, в цикле for или с помощью next(), выполнение функции возобновляется с того же места, где оно было приостановлено, до следующего yield:
Последовательность Фибоначчи - это бесконечная последовательность чисел, где каждое следующее число является суммой двух предыдущих. Это классический пример задачи на генератор.
Задания для самопроверки
1.	Что такое итератор и итерируемый объект?
2.	Объясните, для чего нужны функции iter() и next() Что произойдёт, если next () будет вызвана после того, как все элементы итератора будут получены?
3.	Исправьте ошибку Stopiteration в данном коде и объясните её:
def simple_gen(): yield 'Ты!" yield "Молодец!"
gen = simple_gen()
print(next(gen)) print(next(gen))
print(next(gen))
4.	Напишите функцию-генератор squares_generator(n), которая принимает число п и генерирует квадраты чисел от 0 до п включительно. Используйте её для вывода на экран квадратов чисел от 0 до 5.
5.	Напишите функцию-генератор get_plus(text), которая принимает строку text и при каждом вызове добавляет в конец этой строки строку "+". Трижды вызовите генератор в цикле for для строки "Отлично". Выведите на экран результат каждого вызова
Функции 313
4.8 ЗАМЫКАНИЕ И ДЕКОРАТОРЫ
В Python всё является объектом, поэтому функции могут принимать, и возвращать другие функции, а также одна функция может содержать другую. Всё вместе это лежит в основе таких понятий, как замыкание и декораторы, позволяющие расширять возможности функций.
Замыкания
Вложенная функция всегда имеет доступ к переменным, определенным в её внешней функции. И этот доступ - не просто временная передача значений, а постоянная ссылка на ячейки памяти, где эти значения хранятся.
Ключевой момент, который превращает этот механизм в замыкание, происходит, когда внешняя функция завершает свою работу. В обычной ситуации, когда функция заканчивает выполняться, все её локальные переменные уничтожаются. Однако это не происходит, если выполняются следующие ключевые условия:
о Внутренняя функция использует переменные внешней функции.
о Внешняя функция возвращает внутреннюю функцию.
В таком случае Python понимает, что вложенная функция всё ещё нуждается в переменных из внешней области, и эти переменные «замыкаются» вместе с вложенной функцией. Они не являются копией, а представляют собой прямую связь с исходными данными.
Например, создадим функцию make_adder(), которая принимает число п и возвращает внутреннюю функцию adder() Эта функция принимает число х и возвращает сумму чисел п и х;
def inake_adder(n: int) -> "function": def adder(x: int) -> int: return x + n return adder
Строковая аннотация "function" не является строгим описанием возвращаемого значения, а используется как пояснение для читателя, чтобы подчеркнуть. что функция возвращает другую функцию. Такие аннотации полезны, когда нужные типы ещё не изучены или требуют пмпорз дополнительных инструментов.
Если мы просто вызовем функцию make_adder(), передав ей какое-то
Функции 314
число, ничего не произойдёт, так как она возвращает ссылку на внутреннюю функцию adder(). но мы можем сохранить этот объект в отдельной переменной: adder_5 = make_adder(5) # п = 5 adder_10 = make_adder(10) # п = 10
Здесь каждый вызов make adder() создает свою уникальную копию переменной п, к которой имеет доступ только возвращаемое замыкание. Поэтому ad-der_5 и adder_10 работают независимо друг от друга, и мы можем передать им разные числа:
print(adder_5(10)) # л = 5, х = 10
# вывод: 15
print(adder_10(10)) # п = 10, х = 10 # вывод: 20
Другими словами, несмотря на то, что внешняя функция make_adder() уже завершила свою работу, мы всё равно имеем доступ к её переменным из вложенной функции.
Декораторы
Замыкания находят своё практическое применение в декораторах, которые добавляют новую функциональность к существующим функциям, не изменяя их код. По сути, декоратор - это функция, которая «оборачивает» другую функцию, расширяет её поведение и возвращает новую, изменённую функцию.
Декораторы - эго не отдельный тип объекта, а просто синтаксический сахар, который делает использование функций высшею порядка и замыканий более удобным.
С интаксическим сахаром в Python называют сокращенные формы записи, которые делают код более читаемым и удобным для написания, не меняя при этом логику работы программы. К нему относятся расширенные операторы присваивания, множественное присваивание, списочные выражения, генераторы и декораторы, а также многое другое.
Декоратор представляет собой функцию, которая содержит другую Функции 315
функцию и соответствует следующим условиям:
1. Внешняя функция принимает декорируемую функцию, то есть функцию, функциональность которой расширяется, и возвращает вложенную функцию.
2. Вложенная функция принимает артументы декорируемой функции и вызывает её.
Декорирование функции без параметров
Рассмотрим простой пример, который покажет, как это работает на практике. Представим, что мы хотим, чтобы перед и после вызова любой функции печатались сообщения. Для этого напишем декоратор, который принимает функцию. а во вложенной функции выводит на экран сообщения до и после работы этой функции:
def log_decorator(func: “function") -> "function": def wrapper() -> None: print(f"Функция начинает работу.. ) func() print(f"Работа функции завершена ) return wrapper
Здесь функция log_decorator() принимает функцию func и возвращает вложенную функцию wrapper, которая помнит функцию func благодаря механизму замыкания. То есть после использования декоратора имя функции func будет указывать не на исходную функцию, а на дополненную функцию wrapper. Такую функцию часто называют обёрткой
Для применения декоратора к функции необходимо указать его имя с символом 0 перед ним на строке до объявления этой функции:
(Slogaec orator
def say_smth_nice() -> None:
print("XO4y сказать тебе, что ты молодец!")
say_smth_nice()
#	Вывод: Функция начинает работу...
#	Вывод: Хочу сказать me6ej что ты молодец!
#	Вывод: Работа функции завершена
Здесь, когда мы вызываем функцию say_smth_nice(), на самом деле выполняется вложенная функция декоратора (31og_decorator, которая сначала выводит на экран строку "Функция начинает работу...затем вызывает исходную функцию say_smth_nice(), а после этого выводит строку "Работа функции
Функции 316
завершена".
Декоратор называют синтаксическим сахаром, так как конструкцию (&имя_декоратора можно заменить отдельным вызовом каждой из функций, как мы делали при работе с замыканиями:
say_smth_nice = iog_decorator (say_smth_nice)
say_smth_nice()
#	вывод: Функция начинает работу...
#	Вывод: Хочу сказать тебе, что ты молодец!
#	Вывод: Работа функции завершена
Таким образом, декоратор позволяет расширить работу функции, не изменяя её исходный код.
Декорирование функции с параметрами
Оборачиваемая функция может принимать свои собственные аргументы, с которыми декоратор должен работать. Эти аргументы принимает вложенная функция декоратора, и для того, чтобы декоратор был универсальным можно указывать параметры *args и **kwargs, обозначающие произвольное количество позиционных и именованных аргументов.
Например, напишем декоратор, который проверяет, что все аргументы функции являются положительными числами, иначе - сообщает об ошибке:
def check_positive(func: "function") -> "function":
def wrapper(*args, **kwargs):
if any(arg < 0 for arg in args):
print( Ошибка: переданы отрицательные числа") return None
return func(*args, **kwargs) return wrapper
Здесь вложенная функция wrapper принимает все аргументы декорируемой функции func, и проверяет, что все аргументы args больше нуля Тип возвращаемого значения функции wrapper() зависит оттого, какую функцию мы декорируем, поэтому здесь он не указывается.
Применение этого декоратора к функции, перемножающей два числа, разрешает операцию только над положительными числами:
(S)check_positive
def rtiultiply(x: int, у: int) -> int:
return x * у
resultl = multiply(5, 10) print(f"Результат: {resultl}")
Функции 317
#	Вывод: Результат: 50
result? = multip]y(5, -10)
#	Вывод: Ошибка: переданы отрицательные числа
print(f"Результат: {result?} )
ft Вывод: Результат: None
Здесь вложенная функция декоратора @check_positive принимает все аргументы, которые были переданы в multiply, и вызывает эту функцию только если все числа положительные.
Декораторы с параметрами
Декоратор не обязательно должен быть простым и принимать только одну функцию в качестве аргумента. Также в него можно передать дополнительные параметры, чтобы настроить его поведение. Это делает декораторы более гибкими, однако создание такого декоратора будет несколько сложнее, так как потребуется обернуть основной декоратор в ещё одну функцию, принимающую параметры декоратора.
Такая структура позволяет замыканию работать на двух уровнях: сначала замыкание создается для параметров декоратора, а затем - для декорируемой функции.
Допустим, нам нужно создать декоратор, который будет проверять, имеет ли пользователь необходимые права доступа для вызова функции. Уровень доступа может быть разным, поэтому он должен быть передан как параметр декоратора:
def access_level(required_level: int) -> "function": ft Внешняя функция (принимает параметр декоратора) def decorator(+unc: "function') -> "function":
ft Промежуточная функция (принимает декорируемую функцию) def wrapper(user_level: int, *args, **kwargs):
ft Внутренняя функция (принимает аргументы декорируемой функции) if user_level >= required_level:
print("Доступ разрешен")
return func(user_level, *args, **kwargs) else:
рг1п1("Ошибка доступа")
return None return wrapper return decorator
Здесь декоратор принимает параметр required_level - уровень доступа и, если уровень доступа пользователя, передаваемый из декорируемой функции
Функции 318
больше или равен ему - декоратор разрешает доступ к данным, иначе - запрещает. При этом внешняя функция access_level() выступает в роли фабрики декораторов, то есть она принимает значение параметра и возвращает декоратор - функцию decorator^).
Такой декоратор универсален и позволяет задать любой уровень доступа: @access_leve](required_level=5)
def sensitive_data_access(user_level: int) -> None:
print(f ‘Уровень прав пользователя: {user_level}")
# Какая-то очень секретная логика
sensitive_data_access(user_level=10)
#	Вывод: Доступ разрешен.
#	Вывод: Уровень прав пользователя: 1в
sensitive_data_access(user_level=3,)
#	Вывод: Ошибка доступа
Здесь уровень прав доступа для вызова функции sensitive_data_access() должен быть выше 5, поэтому декоратор вызывает функцию для пользователя с уровнем 10, но сообщает об ошибке доступа для пользователя с уровнем 3.
Примеры
Пример 1. Калькулятор налога на доходы физических лиц
В разных странах величина налога на доходы физических лиц отличается. Например, в России он может быть равен 13 %, а в США - 22 % (число может меняться в зависимости от условий). Решение задачи разработки калькулятора, подсчитывающего НДФЛ, может решаться напрямую путём создания отдельной функции для каждой страны:
def calculare_tax_rus(salary: int) -> float: """Возвращает НДФЛ в России (I II II
return salary * 0.13
def calculate_tax_usa(salary: int) -> float:
...Возвращает НДФЛ в Америке II II II
return salary * 0.22
print(f”Han -г в России на 1000 рублей: {calculate_tax_rus(1000)} руб.") print(f"Налог в США на 1000 долларов: {calculate_tax_usa(100r)} J' )
Функции 319
Вывод:
Налог в России на 1000 рублей: 130.0 руб.
Налог в США на 1000 долларов 220.0 $
Но если стран много, то такой подход может оказаться не очень удобным. Однако с помощью замыкания можно использовать такой шаблон проектирования, как фабрика функций. Вместо того чтобы создавать несколько похожих функций, вы создаете одну фабрику, которая производит их по требованию:
def tax_calculator_factory(tax_rate: float) -> "function”:
...Возвращает НДФЛ в зависимости от ставки. I* м н
def calculate_tax(salary: int) -> float: return salary * taxrate
return calculate_tax
#	Создание функции для расчёта НДФЛ по ставке 13 %
calculate_tax_rus = tax_caiculator_factory(0.13)
print(f"Налог в России на 1000 рублей: -(calculate_tax_rus(1000)} руб.")
# Создание функции для расчета НДФЛ по ставке 22 %
calculate_tax_usa = tax_calculator_factory(e.22)
print(f"Hanor в США на 1000 долларов: {ralculate_tax_usa(1000)} $")
Вывод:
Налог в России на 1000 рублей: 130.0 руб.
Налог в США на 1000$: 220.0$
Пример 2. Мемоизация результатов функции
Мемоизация - это техника оптимизации, при которой результаты вызовов функции сохраняются (кэшируются) в словаре (кэше) и возвращаются при повторном вызове с теми же входными данными, вместо повторного вычисления. Это значительно ускоряет выполнение функций.
Декоратор memoize() хранит все результаты вызовов функции func в словаре cache. Вложенная функция wrapper сначала проверяет, есть ли результат для текущих аргументов в кэше. Если есть - возвращает его, иначе - вызывает исходную функцию, сохраняет результат в кэше и только потом возвращает.
def memoize(func: "function") -> "function":
..Декоратор для кэширования результатов функции. »« W и
cache = {}
def wrapper(*arEs):
"""Внутренняя функция декоратора, которая принимает
Функции 320
позиционные аргументы декорируемой функции и использует их (кортеж args) в качестве ключа словаря с кэшем.
II II II
if args in cache:
print('Возвращаем результат из кэша... ")
return cache[args]
print("Вычисляем результат.. )
result = func(*args)
cache[args] = result
return result
return wrapper
(Smemoize
def expensive_calculation(n: int) -> int:
...Возвращает число n, возьедённие в степень 10.
Параметры:
п: Число, которое возводится е степень.
возвращает
Число п в степени 10.
II II II
return п ** 10
# Вызов функции несколько раз с одинаковыми аргументами
expensive_calculation(5)
expensive_calculation(5)
exp?nsive_calculation(10)
expensive_calculation(10)
expensive_calculati on(5)
Вывод:
Вычисляем результат...
Возвращаем результат из кэша...
Вычисляем результат...
Возвращаем результат из кэша...
Возвращаем результат из кэша...
Пример 3. Форматирование вывода
Система управления логами позволяет форматировать результат, возвращаемый функцией, например, добавляя префикс или суффикс, или переводя текст в верхний регистр.
Внешняя функция декоратора format_output() принимает параметры декоратора: строку prefix, которой должен начинаться вывод, строку suffix.
Функции 321
которой должен заканчиваться вывод, и флаг to upper для приведения к верхнему регистру. Вложенная функция wrapper() вызывает исходную функцию, а затем применяет к ее результату настройки форматирования, переданные при применении декоратора к функции:
def format_output(prefix: str="", suffix: str»"", to_upper: bool=False):
...Декоратор с параметрами ля форматирования вывода результата функции.
Параметры:
prefix: Строка, которой начинается вывод результата функции.
suffix: Строка, которой заканчивается вывод результата функции.
to_upper: Флаг приведения к верхнему регистру.
По умолчанию False, то есть регистр не меняется.
Il 11 II
def decorator(func):
def wrapper(*args, **kwargs):
result = str(func(*args, **kwargs)) if to_upper:
result = result.upper()
return f' fprefix}{ result}{suffix}"
return wrapper return decorator
(S)format_output(prefix="-> , suffix»" <-", to_upper=True) def get_user_status(status: str) -> str:
...Возвращает отформатированную строку co статусом пользователя.
Параметры:
status: Статус пользователя.
возвращает:
Отформатированная строка.
II II и
return f Статус пользователя: {status}"
print(get_user_status("активен' ))
|3format_output(prefix=" [INFO]: ")
def get_log_message(message: str) -> str:
"""Возвращает отформатированную строку с системным сообщением.
Параметры:
message: Системное сообщение.
Возвращает:
Отформатированная строка.
Функции 322
II II II
return f“flor: {message}'*
print(get_log_message("Система запущена"))
Вывод:
-> СТАТУС ПОЛЬЗОВАТЕЛЯ: АКТИВЕН <-[INFO]: Лог: Система запущена
Итоги
J Замыкание в Python относится к функциям, которые содержат другие функции. Оно заключается в том, что вложенная функция, имеет доступ к переменным внешней функции даже после завершения выполнения этой функции,
s Замыкание происходит, если внутренняя функция использует переменные внешней функции, а внешняя функция возвращает внутреннюю функцию. Декоратор - это функция высшего порядка, которая изменяет поведение другой функции, не изменяя её исходный код.
Внешняя функция декоратора принимает декорируемую функцию и возвращает вну1реннюю функцию, которая дополняет функциональность этой функции.
Вложенная функция декоратора принимает аргументы декорируемой функции и вызывает её.
Конструкция (Эимя_декоратора на строке до создания функции применяет к ней этот декоратор.
J Декоратор с параметрами создается путем оборачивания его в ещё одну функцию, которая принимает параметры декоратора и возвращает сам декоратор.
Задания для самопроверки
1.	Что такое замыкание? Какие условия должны быть выполнены для его создания?
2.	Объясните, что такое декоратор и из чего он состоит.
3.	Найдите все ошибки в этом коде и исправьте их гак, чтобы на экран были последовательно выведены числа 1,2 и 3.
def create_counter() -> "function":
Функции 323
count = 0
def incrementO -> int:
count += 1
return count
return increment
counter = incrementO
print(counter())
#	Вывод: 1
print (counterQ)
#	Вывод: 2
print(counter())
#	Вывод: 3
4.	Допишите декоратор print start_end(), который выводит сообщение "Начало выполнения" перед вызовом декорируемой функции process_data() и "Конец выполнения" после её завершения:
def print_start_end():
pass
@print_start_ena
def process_data(data: str) -> None:
print(f"обработка данных: {data}")
process_data( Секретные материалы")
5.	Допишите декоратор repeat (п), который принимает число п и повторяет вызов декорируемой функции say_a_poem() п раз.
def repeat():
pass
|9repeat(n=3)
def say_a_poem() -> None:
print("Однажды, в студёную зимнюю пору\п"
"Я из лесу вышел; был сильный мороз.\п")
say_a_poem()
Функции 324
Глава 5
Объектно-ориентированное программирование
5.1 КЛАССЫ И ОБЪЕКТЫ
К этому моменту вы уже освоили основные инструменты Python: типа данных, условия, циклы и функции. Вы научились писать программы, которые решают вполне конкретные задачи, например, обрабатывают список чисел или создают словарь. Это основа, которая позволяет создавать простые, но полезные программы.
До этого момента мы работали со значениями встроенных типов данных: числами, булевыми значениями, строками и другими коллекциями, а также отдельно писали функции для работы с ними. Допустим, мы разрабатываем про-1рамму для автосалона и нам нужно описать продаваемые автомобили. У каждого автомобиля есть характеристики (модель, наличие на складе и цена) и действия, которые можно над ним выполнить (установить скидку и продать).
Тогда характеристики автомобиля будем хранить в словаре: lada_granta = {
"Модель": "Lada Granta Седан ,
"Цена": 80Ь_0Ой, "Наличие : True }
А действия, совершаемые над ним, напишем в виде отдельных функций. Пусть функция aplly_dicsount() принимает словарь саг с данными об автомобиле и скидку percentage в процентах, а возвращает новую цену:
def apply_discount(car: diet, percentage: int) -> int | float: discountamount = саг[ Цена"] * (percentage / 10e) newprice = саг["Цена'] - discountamount print(f"Новая цена автомобиля {саг["Модель"]}: {new_price} руб.")
return new_price
А функция продажи автомобиля sell() также принимает словарь саг и изменяет и возвращает статус наличия автомобиля
def sell(car: diet) -> bool:
Объектно-ориентированное программирование 325
if саг["Наличие"J:
print(f'Автомобиль (car['Модель']} успешно предан') return False
print(f Автомобиль {car["Модель"]} уже был продан")
return True
В таком случае для совершения действий над автомобилем следует не только вызывать функции, но и обновлять данные в словаре:
lada granta["Цена'] = apply_discount(lada_granta, 10)
#	Вывод: Новая цена автомобиля Lada Granta Седан: 728066.б руб.
lada_granta["Наличие ] = sell(lada.granta)
#	Вывод: Автомобиль Audi АЗ III успешно продан
Мы с вами уже не раз писали код в гаком стиле, и. по сути, следовали парадигме процедурною программирования, когда программа основана на последовательном вызове функций (процедур), но при этом данные и функции существуют отдельно друг друга.
Слово «парадигма» (от греч. paradeigma - шаблон, пример) в программировании означает определенный стиль или подход к написанию кода. Это как разные философские школы: каждая предлагает свой способ мышления и решения задачи. Другими словами, в программировании парадигма - это концептуальная модель, которая определяет, как будет строиться ваша программа. Если вы пишете код в процедурном стиле, вы думаете о нем как о серии шагов.
Но что. если мы хотим объединить и значения, и функции для работы с ними? Ведь было бы намного удобнее объединить характеристики автомобиля и действия над ним в один объект, а не скрупулёзно отслеживать обновление данных в словаре.
И это возможно, если мы обратимся к концепции объектно-ориентированного программирования (ООП), одним из основных принципов которого является объединение данных и функций в единое целое - объект.
В ООП вы думаете о своей программе как о системе взаимодействующих Объектно-ориентированное программирование 326
объектов. Они являются основными строительными блоками, вокруг которых организуется вся логика. Таким образом, вместо того чтобы сосредотачиваться на том. как выполнить определенный шаг, вы думаете о том. какие сущности существуют и вашей программе (например, продавец или автомобиль), какие у них есть свойства и что они могут делать.
Классы и объекты
Любой объект создаётся на основе класса, который является шаблоном, определяющим свойства и поведение объекта. Поэтому такой объект ещё называют экземпляром класса. Может быть вы помните, что функция type() возвращает конструкцию class <тип> Это связано с тем, что все встроенные типы данных являются классами. Например, строка "Привет! " является объектом или экземпляром класса str и поддерживает всего его свойства и методы.
И мы можем не только использовать встроенные классы, но и создавать свои собственные. Давайте создадим класс для автомобиля в автосалоне. Для этого нам понадобится ключевое слово class, за которым следует имя класса, написанное с большой буквы. Если названия переменных и функций мы писали в «змеином регистре» snake_case, то классы принято именовать в «верблюжьем регистре» CamelCase. То есть каждое слово начинается с заглавной буквы и без разделителей следует за предыдущим:
class Саг:
pass
Здесь мы создали пустой класс Саг с помощью уже знакомого нам ключевого слова pass.
Сам по себе класс - это просто шаблон. Для того чтобы с ним работать, следует создать его объект, вызвав имя класса, за которым следуют круглые скобки:
ladagranta = Саг()
Это выражение создаёт новый объект класса Саг() и присваивает его переменной lada_granta. Мы можем убедиться в этом с помощью функции гуре(): print(type(lada_granta)) # Вывод: <cLass '_____main__.Car‘>
Объектно-ориентированное программирование 327
Атрибуты и методы объекта
Однако для полноценной работы нам потребуются атрибуты и методы экземпляра класса.
Атрибуты объекта и мето,т___init_()
Атрибуты объекта - это переменные, предназначенные для хранения данных о каждом конкретном объекте класса. В нашем примере с автомобилем атрибутами должны быть переменные model для хранения модели, price - для цены и in_stock - для статуса наличия автомобиля на складе.
Для того, чтобы создать или получить значение атрибута, достаточно указать его имя после имени объекта, разделив их точкой:
ladagranta.model = "Lada Granta Седан"
lada_granta. price = 800_00U
lada_granta.in_stock = True
Здесь для объекта lada_granta были созданы новые атрибут model, price и in_stock.
Также при создании объекта Python создает словарь___diet__, в который
добавляет все атрибуты этого объекта:
print(laua_granta._diet_)
# Вь/tiod: {'modeL': ‘Lada Granta Седан', 'price': 800000, ' in_stock': True)
Для того, чтобы задать начальные значения атрибутов объекта нам необходим магический метол _____init__(), называемый конструктором класса.
Название init (от англ, initialize - инициализировать) говорит само за себя - его основной задачей является инициализировать атрибуты нового объекта, то есть задать их начальные значения.
Первым параметром любого метода объекта, в том числе метода __init__(), всегда должна быть ссылка на конкретный объект, для которого вызывается метод. Её принято обозначать с помощью слова self, однако это не ключевое слово, как def или class, а скорее общепринятое соглашение об именовании, которого придерживаются все разработчики Python.
Объектно-ориентированное программирование 328
Методы, которые позволяют настраивать встроенное поведение класса, принято называть магическими методами или dunder-методами (от англ, double underscores - двойное нижнее подчёркивание), так как они всегда начинаются и заканчиваются двумя нижними подчёркиваниями. Такие методы вызываются автоматически интерпретатором Python в ответ на определённые события. Например, метод___Init__()
ма1ически вызывается при создании объекта класса.
Параметр self позволяет обращаться ко всем атрибутам и методам объекта. в том числе, присвоить им повое значение.
Как и в обычных функциях, при определении методов класса, в том числе метода___init__(). мы можем использовать аннотации типов. При этом метод
__init___() всегда возвращает None, поэтому для него допускается не указывать тип во звращаемого значения. Также на классы распространяются те же рекомендации по количеству пустых строк: от другого кода класс отделяется двумя пустыми строками, а методы и логические блоки внутри класса разделяются одной пустой строкой: class Саг:
def___init_(self, model: str, price: int | float, in_stock: bool=True):
self.model = model self price = price self.instock = in_stcck
Здесь метод____init__() принимает два обязательных параметра: model и
price и один необязательный параметр in_stock. Они передаются классу при создании новою объекта:
lada_granta = Car( Lada Granta седан , 8и0_иии)
При создании экземпляра класса Саг автоматически вызывается метод __init___(), который присваивает переданные значения атрибутам созданного объекта, так как параметр self указывает на экземпляр класса, для которого применяется метод.
Объектно-ориентированное программирование 329
Методы объекта
Методы объекта - это функции, описывающие поведение объекта класса. Мы уже писали ранее функции применения скидки apply_discount() и продажи sell(). на основе которых мы можем создать методы для всех объектов класса Саг.
В тексте не единожды уточнялось, что мы рассматриваем атрибуты и методы именно объекта или экземпляра класса, а не самого класса. Это связано с тем. что в Python существуют также атрибуты и методы класса в целом, с которыми мы познакомимся позже.
Методы экземпляра класса создаются как обычные функции, но первым параметром они должны принимать ссылку self.
Тогда метод apply_discount() для применения скидки должен принимать только значение скидки percentage, так как все атрибуты объекта доступны но ссылке self, а метод sell() для продажи автомобиля изменяет атрибут in_stock объекта, поэтому ему достаточно только параметра self:
class Car:
def___init__(self, model: str, price: int | float, in_stock: bool=True):
self.model = model self.price = price self.in_stock = in_stock
def apply_discount(self, percentage: int | float) -> None: discount_amount = self.price * (percentage / 160) self.price -= discountamount
print(f"Новая цена аьтомобиля {self.model}: {self.price} руб. )
def sell(self) -> bool: if self.in_stock: print(f“Автомобиль {self.model) успешно продан") self.in_stock = False return True
print(f'Автомобиль {self.model) уже был продан") return False
Объектно-ориентированное программирование 330
Мы дополнили класс Саг как атрибутами, так и методами, и теперь он готов к работе. Больше нет необходимости работать с разрозненными словарями и функциями, а вся логика, связанная с продажей автомобилей организована в один класс.
Давайте создадим несколько объектов класса Саг:
lada_granta = Car( Lada Granta Седан’, 8и0_00О)
audi_a3 = Car( Audi АЗ III", 1_500_000, False)
И вызовем методы напрямую у объектов:
toyota_corolla = Car( 'Toyota Corolla XII , 2_115_000) sellerl = Seller('Барто Агния )
#	Объект seLLerl взаимодействует с объектом toyota_coroLLa: sellerl.sell_car(toyoca_corolla)
#	Вывод: Автомобиль Toyota Corolla XII успешно продан
#	Вывод: Продавец Барто Агния продал автомобиль Toyota CoroLLa XII
При этом не обязательно присваивать созданные объекты отдельным переменным, хотя мы и не сможем управлять состоянием такого объекта:
Car( "Renault Lagan II' , 380_00О).sell()
#	Вывод: Автомобиль Renault Logan II успешно продан
Взаимодействие объектов в ООП
В отличие от других подходов, ООП рассматривает программу не как последовательность инструкций, а как набор взаимодействующих объектов. Например, созданный ранее класс Саг описывает как характеристики продаваемых автомобилей, так и действия, которые над ними можно совершать.
Однако в автосалоне есть не только автомобили, но и продавцы, которые их продают. Было бы логично описать продавца как отдельную сущность, или объект, который может совершать различные действия, в том числе и продажу автомобиля. Для этого напишем класс Seller, который инициализируется атрибутом name с именем продавца, и у которого есть метод sell_car() для продажи автомобиля-
class Seller:
def__init__(self, name: str):
self.name = name # Имя продавца
def make_sell(self, car: Car) -> None:
# Вызываем метод sei I() объекта car
if car.sell(): # Если метод вернул True, то выводим сообщение print(f Продавец {self.name} продал автомобиль {car.model}")
Объектно-ориентированное программирование 331
return True
return False
Метод make_sell() в классе Seller принимает в качестве аргумента саг, который является объектом класса Саг, и вызывает его метод sell(). Это ключевой момент, который демонстрирует, как объекты могут взаимодействовать.
Давайте создадим новые объекты классов Саг и Seller, у которого вызовем метод make_sell()‘
toyota_corolla = Car("Toyota Corolla XII , 2_115_000) sellerl = Seller( 'Барто Агния )
#	Объект seLLerl взаимодействует с объектом toyova_coroLLa: sellerl.sell_car(toyota_corolla)
#	Вывод: Автомобиль Toyota CoroLLa XII успешно продан
#	Вывод: Продавец Барто Агния продал автомобиль Toyota CoroLLa XII
Теперь программа состоит из независимых, но взаимодействующих сущностей - объекта toyota_corolla класса Саг и объекта sellerl класса Seller.
Основные принципы ООП
Но ООП не ограничивается простым взаимодействием объектом, так как в его основе лежат три основных принципа - инкапсуляция, наследование и полиморфизм. Давайте кратком рассмотрим каждый из них, так как позже мы познакомимся с ними подробнее.
Инкапсуляция
Инкапсуляция - это принцип, который объединяет данные и методы, работающие с этими данными, в единую структуру, называемую классом. Главная идея заключается в скрытии внутренней реализации объекта от внешнего мира и предоставить к нему доступ только через строго определенный интерфейс, то есть набор методов
Это как включение компьютера, вы просто нажимаете на кнопку и вам не нужно знагь, как именно он работает. В ООП то же самое: вы взаимодействуете с объектом через его публичные методы, не вникая в его внутреннее содержание. Это помогает защитить данные от случайного изменения.
Объектно-ориентированное программирование 332
Наследование
Наследование - это механизм, который позволяет одному классу перенимать атрибуты и методы другого класса. Это помогает избежать дублирования кода и создает иерархию классов.
Например, если мы создаём программу для зоопарка, то можем создать базовый класс для животных Animal, который будет содержать общие для всех животных атрибуты, например, имя name или возраст age, и методы, например, кормление feed(). Затем мы можем создать класс для птиц Bird, который унаследует всё от Animal, но при этом добавит свои уникальные свойства, например, размах крыльев wingspan. Такой подход позволяет избежать дублирования кода.
Полиморфизм
Полиморфизм - это способность разных объектов использовать один и тот же метод для выполнения различных действий. То есть, один и тот же вызов метода может привести к разным результатам в зависимости от типа объекта, на котором он был вызван.
Представьте, что у нас есть классы Саг для автомобиля и ElectricCar для электромобиля, и у каждого из них есть метод get_fuel_level(), чтобы узнать, сколько топлива у них осталось. Но для автомобиля метод возвращает количество бензина, а для электромобиля - процент заряда аккумулятора. Хотя метод называется одинаково, он ведёт себя по-разному в зависимости оттого, на каком объекте был вызван. То есть полиморфизм позволяет нам работать с разными объектами через единый интерфейс.
Примеры
Пример 1. Проверка роли пользователя
В системе каждый пользователь описывается классом User, у которого есть атрибуты с именем username и ролью role. Для доступа к определённым ресурсам роль пользователя должна соответствовать требуемой роли, что проверяется с помощью метода has_permission();
class User:
"""Описывает пользователя системы и его роль."""
def__init_(self, username: str, role: str):
"""Конструктор класса User.
Параметры:
Объектно-ориентированное программирование 333
username: Уникальное имя пользователя, role: Роль пользователя в системе. II II II self.username = username self.role = role.lower()
def has_permission(self, required_role: str) -> Pool: """Проверяет, соответствует ли роль пользователя требуемой.
Параметры:
requiredrole: Роль, необходимая для выполнения действия.
Возвращает:
True, если пользователь имеет необходимые права, иначе False. II II II
required_role = required_role.lower()
# Проверяем точное совпадение return self.role == required_role
# Создание объектов
userl = User(‘‘bartenev7", "Администратор")
user2 = User("aivazovsky8", "Модератор")
# Проверка прав доступа
print(f"Пользователь {userl.username} имеет доступ к панели администратора?
{userl.has_permission('Администратор')}")
print(f"Пользователь (user2.username} имеет доступ к базе данных?
{user2.has_permission('Пользователь')}")
Вывод:
Пользователь bartenev7 имеет доступ к панели администратора? True
Пользователь aivazovsky8 имеет доступ к базе данных? False
Пример 2. Доставка посылки
Посылки в курьерской службе описываются классом Package, и каждую посылку можно отметить доставленной с помощью метода mark_delivered(), который изменяет статус этой посылки, то есть атрибут status.
class Package: ......Описывает посылку и ее состояние."""
def __init_(self, tracking_id: str, destination: str, status: str= .
пути"):
"""Конструктор класса Package.
Параметры:
Объектно-ориентированное программирование 334
track!ng_id: Уникальный идентификатор посылки, destination: Адрес доставки.
status: Статус посылки. По умолчанию "В пути".
self.tracking_id = tracking_id self.destination = destination self.status = status
def mark_delivered(self) -> bool:
"""Меняет статус посылки на 'Доставлено1, если она еще не доставлена.
Возвращает:
True, если статус успешно обновлен, False, если уже доставлена, ft it it
if self.status != "Доставлено’": self.status = "Доставлено" print( 'Посылка доставлена ) return True
print('Посылка уже была доставлена' ) return False
packagel = Package("2358AZ", "Тульская обл., г. Тула, ул. Ленина, д. 10") package!.mark_delivered() packagel.mark_delivered()
Вывод:
Посылка доставлена
Посылка уже была доставлена
Пример 3. Управление запасами товаров в интернет-ма1 азине
В системе учёта товаров на складе интернет-магазина каждый товар описывается классом Product и имеет атрибуты с ценой price и количеством quantity, которые должны быть неотрицательными числами. Также к цене товара можно применить скидку и изменить значение атрибута price с помощью метода apply discount(), а проверить наличие товара позволяет метод check_stock():
class Product:
...Описывает товар на складе магазина."""
def __init__(self, name: str, price: int, quantity: int):
“""Конструктор класса Product.
Параметры:
name: Наименование товара.
price: Цена товара.
quantity; Количество товара на складе.
Объектно-ориентированное программирование 335
# Проверка данных при создании объекта
if price <= Э or quantity < 0:
raise ValueError('Цена и количество товара должны быть положительными числами")
self.name = name
self.price = price
self.quantity = quantity
def apply_disc<iunt(self, percent: int) -> Nene:
"""Применяет скидку к цене товара, обновляя атрибут price.
Параметры:
percent: Скидка на товар в процентах.
It II II
discount_amount = self.price * (percent / 100)
self.price -= discount_amount
print(f"Скидка {percent}^ применена. i-k> ая цена '{self.name} .
{self.price:.2f} руб. )
def check_stock(self) -> bool:
"""Проверяет, есть ли товар в наличии.
Возвращает:
True, если товар есть в наличии, иначе False.
II II II
return self.quantity > 0
#	Создание объектов
keyboard = Product("Клавиатура игровая мембранная проводная", 791, 50) monitor = Product('Монитор игровой 21.5", 15000, 15)
#	Взаимодействие с объектами
print(f Наличие клавиатуры: {keyboard.check_stock()}') # True
monitor.apply_discount(15)
#	Попытка создать объект с некорректными данными
try:
bad_pr,oduct = Product("Кабель USB Туре-С", -150, 5)
except ValueError as e:
print(f'Ошибка при создании товара: {е}')
Вывод:
Наличие клавиатуры: True
Скидка 15% применена. Новая цена 'Монитор игровой 21.5': 12750.00 руб.
Ошибка: Цена и количество товара должны быть положительными числами
Объектно-ориентированное программирование 336
Ито1 и
J Процедурное npoi раммирование - это парадигма программирования, основанная на использовании функций для организации кода.
S Объектно-орнентированное программирование (ООП) - это парадигма программирования, основанная на использовании объектов для организации кода.
S Объект - это сущность, объединяющая данные и функции
J Класс - это шаблон, определяющий свойства и поведение объекта.
А । рибуты объек га - это переменные, предназначенные для хранения данных о каждом конкретном объекте класса.
J Mai ические методы - это методы, позволяющие настраивать встроенное поведение объектов класса.
S Магический метод__init__() называется конструктором класса и позво-
ляет задать начальные значения атрибутов нового объекта.
J Методы объекта - это функции, описывающие поведение объекта класса.
J Первым параметром метода объекта должна быть ссылка на конкретный объект, для которого вызывается метод. Такой параметр принято называть self.
✓ Параметр self позволяет обращаться ко всем атрибутам и методам объекта,
S В основе ООП лежат три основных принципа - инкапсуляция, наследование и полиморфизм.
J Инкапсуляция - это принцип, который объединяет данные и методы, работающие с этими данными, в единую структуру, называемую классом.
S Наследование - это механизм, который позволяет одному классу перенимать атрибуты и методы другого класса.
J Полиморфизм - это способность разных объектов использовать один и тот же метод для выполнения различных действий.
Задания для самопроверки
I.	Чем ООП отличается от процедурного программирования?
2.	Объясните разницу между классом и объектом (экземпляром класса) на примере из реальной жизни.
3.	Создайте класс для точки Point, который инициализируется атрибутами
Объектно-ориентированное программирование 337
х и у с соответствующими координатами. Создайте объект этого класса с атрибутами х = 5иу » 10. Выведите содержимое словаря_diet_для этого объекта.
4.	Создайте класс для книги Book, который инициализируется атрибутами title с названием книги и author с именем автора. Создайте два разных объекта этого класса и выведите на экран значение title для каждого из них.
5.	Создайте класс для лампы Lamp, который инициализируется атрибутом is_on со значением по умолчанию False. Он характеризирует состояние лампы: включена (True) или выключена (False). Создайте в этом классе метод tog-gle_switch(). который меняет значение атрибута is_on на противоположное (True на False и наоборот). Создайте объект этого класса и вызовите метод дважды. выводя состояние is_on после каждого вызова.
5.2 ИНКАПСУЛЯЦИЯ И ОГРАНИЧЕНИЕ ДОСТУПА
Инкапсуляция - это один из грех основных ООП. И как мы уже говорили, её главной идеей является сокрытие внутренней реализации объекта от внешнего мира и предоставление контролируемого доступа к его данным.
Вспомним пример с автомобилем из предыдущего параграфа. Инкапсуляция позволяет контролировать, как и когда данные объекта могут быть изменены. Так. вызов метода продажи sell() не только изменяет статус наличия автомобиля в автосалоне, но и выполняет другие необходимые действия, например, выводит сообщение о продаже. Прямое изменение атрибута не обеспечит совершение этих действий.
Ограничение доступа к данным
В отличие от некоторых других языков программирования, в Python нет строгих механизмов для полного запрета доступа к атрибутам и методам. Вместо этого используется соглашение об именовании, которое говорит разработчикам. как следует обращаться к этим данным:
о Публичные (англ. - public) атрибуты и методы не начинаются с подчеркиваний. например, apply_discount(). Они доступны как внутри класса, так и вне его. Вы можете свободно изменять значения таких атрибутов и использовать методы в любом месте программы.
о Защищённые (англ. - protected) атрибуты и методы начинаются с одного подчеркивания, например, model Они считаются доступными только Объектно-ориентированное программирование 338
внутри класса и во всех его дочерних классах. Однако технически Python не мешает вам обращаться к таким атрибутам и методам напрямую извне класса, так как такое обозначение является просто сигналом для других разработчиков.
о Приватные (англ. - private) атрибуты и методы начинаются с двух нижних подчеркиваний, например. _________price. Они доступны только внутри
класса Python применяет к таким именам механизм сокрытия, чтобы затруднить прямой доступ. Он автоматически меняет имя атрибута, добавляя к нему имя класса, поэтому атрибут _________price становится атрибутом
_Саг___price.
Давайте модифицируем класс Саг из предыдущего параграфа, сделав атрибут model защищённым, а атрибут in_stock - приватным:
class Саг:
def _init_(self, model: str, price: int | float, in_stock: bool=True)
self._model = model self._price = price self._in_stock = in_stock
def apply_discount(self, percentage: int | float) -> None: discount_amount = self._price * (percentage / 100) self._price -= discount_amount print(f"Hoeая цена автомобиля {self.model}• {self._price} руб.")
def sell(self) -> bool: if self._____in_stock:
print(f"Автомобиль {self.model} успешно продан") self._in_stock = False
return True
print(f'Автомобиль {self.model} уже был продан") return False
Так. мы можем напрямую получить значение защищённого атрибута, но для приватного атрибута вызывается исключение AttributeError:
honda_accord = Car( "Honda Accord VII , 800 000) print(honda_accord._model) it Вывод: Honda Accord VII print(honda_accord.__price)
# Ошибка: AttributeError: 'Car' object has no attribute '__price'
При этом вы можете попробовать напрямую изменить значение приватного атрибута и вам даже покажется, что всё работает: honda_accord.________price = 700_000
Объект но-ориентированное программирование 339
print(honda_accord._price)
#	Вывод: 700000
Однако на самом деле вы не меняете атрибут____price, который определён
внутри класса Саг. Вместо этого. Python создает новый атрибут, который привязан только к экземпляру honda_accord, а не к классу Саг. Вы можете убедиться в этом, получив словарь______diet_, который хранит все атрибуты объекта:
print (honda_accord._diet_)
#	Вывод: {'modeL': 'Honda Accord VII', ’_Car _price ': 800000, ‘_Car___in_stock': True, '_price': 700000}
Как видите, объект honda_accord содержит новый атрибут____price, а также
атрибут Саг___price, который как раз и является атрибутом___price, используе-
мым в классе.
Однако так как Python скрывает атрибут, просто добавляя к нему имя класса, го мы можем напрямую обратиться к его новому имени и изменить его: honda_accord._Car__________price = 700_00О
print (honda_accord._diet_)
#	Вывод: {'modeL': 'Honda Accord VII', ‘_Car_price': 700000,
'_Car_in_stock': True, '___price': 700000}
Но так делать не стоит, ведь изменение имени защищает внутренние атрибуты класса от случайного или намеренною изменения извне.
Геттеры и сеттеры
Для того чтобы обеспечить контролируемый доступ к атрибутам, особенно к тем. которые мы пометили как приватные, в ООП используются специальные методы: геттеры и сеттеры:
о Геттер (от англ, get - получить) - это метод, который позволяет получить значение атрибута.
о Сеттер (от англ, set - установить) - это метод, который позволяет установить или изменить значение атрибута, но при этом позволяет добавить дополнительную логику для проверки или преобразования данных перед их сохранением.
Такие методы позволяют не только обращаться к приватным атрибутам, но и добавлять дополнительные действия к операциям чтения и записи. Например, проверку того, что цена автомобиля строго больше нуля.
Давайте напишем геттер get_price() и сеттер set_price() для получения
Объектно-ориентированное программирование 340
и изменения цены автомобиля. Тогда класс Саг будет выглядеть следующим образом (пропустим ненужные нам сейчас методы apply_discount() и sell()):
class Car:
def__init__(self, model: str, price: int | float, in_stock: bool=True):
self._model = model self.__price = price
self.__in_stock = in_stock
#	Геттер для цены
def get_price(self) -> int | float: return seif._____price
#	Сеттер для цены
def set_price(self, new_price: int | float) -> None:
if new_price > 0:
self._price = new_price
else:
print( Цена должна быть положительным числом")
Геттер является обычным методом, который возвращается значение нужного атрибута, поэтому метод get_price() возвращает значение приватного атрибута ___price.
Сеттер же преднашачен для установки значения атрибута, однако также позволяет добавить новую логику, поэтому метод set_price() изменяет значение атрибута _price только в том случае, если новое значение больше нуля.
Теперь мы можем как получить, так и изменить значение приватного атрибута __price:
chevrolet_suburban = Car( 'Chevrolet Suburban XII , 13_000_00o) chevrolet_suburban.set_price(12_500_000) pri nt(chevrt -let_suburban.get_price()) # Вывод- 12500000
Также сеттер set_price() проверяет корректность данных перед их сохранением и значение не будет изменено, если новая цена меньше или равна нулю: chevrolet_suburban.set_price(-10_ 0Э0_00О) # Вывод: Цена должна быть положительным числом print(chevrolet_suburban.get_price()) # Вывод: 12500000
Декораторы (^property
Явно вызывать геттеры и сеттеры как методы - это стандартная практика, но в Python есть более изящный способ их реализации с помощью декоратора (Sproperty. Его использование позволяет обращаться к методу как к атрибуту.
Объектно-ориентированное программирование 341
Первый метод, который вы помечаете декоратором @property. становится геттером, а сеттер создается с помощью декоратора @имя_геттера.setter. При этом оба метода должны иметь одинаковое название - имя атрибута, к которому мы будем обращаться.
Тогда методы get_price() и set_price() следует переименовать просто в price(), а также декорировать геттер как ^property, а сеттер - как @price. setter: class Car:
def __init_(self, mode]: str, price: int | float, in_stock: bool |
None=True):
self._model = model
self.__price = price
self.__in_stock = in_stock
#	Геттер ^property def price(selt) -> int | float: return self._____price
#	Сеттер
Sprite.setter
def price(self, new_price: int I float) -> None:
if new_price > 0:
self._price = new_price
else:
print( Ошибка: цена должна быть положительным числом )
Теперь мы можем работать с ценой, как с обычным атрибутом, хотя на самом деле за кулисами вызываются геттер и сеттер:
hyundai_solaris = Car( Hyundai Solaris II' , 1_150_000)
byundai_solaris.price = 1_000_000
print(hyundai_solaris.price)
#	ВывоО: 1000000
Здесь атрибут price вызывает геттер price() с декоратором ^property для получения значения атрибута и сеттер price() с декоратором price. setter() для установки нового значения атрибута.
Преимущество такого подхода заключается в том, что мы получаем все преимущества инкапсуляции, но без усложнения синтаксиса для пользователя класса. Внешне, работа с атрибутом_____price выглядит так же, как работа с обыч-
ными атрибутами, но внутри мы полностью контролируем какие данные будут сохранены или возвращены.
Объектно-ориентированное программирование 342
Примеры
Пример 1. Изменение кредитною лимита
Для управления кредитным лимитом используется класс CreditAccount. где атрибут с кредитным лимитом_________limit является приватным. Сеттер с деко-
ратором (a)limit.setter проверяет, что новый кредитный лимит является положительным числом:
class CreditAccount: .......Описывает управление кредитным лимитом.....
def___init__(self, owner: str, limit: int):
..Конструктор класса CreditAccount.
Параметры:
owner: Номер счета.
limit: Текущий кредитный лимит, tt fl II self.owner = owner self.___limit = limit # Приватный атрибут
(^property def limit(self) -> int: """Геттер. Возвращает значение приватного атрибута _______limit....
return self.__limit
@limit.setter
def limit(self, new_limit: int) -> None:
"""Сеттер. Устанавливает новое значение атрибута кредитного лимита __limit, если оно является положительным числом.
Параметры:
new_limit: Новое значение кредитного лимита. II II II
if new_limit >= 0: self.______limit = new_limit
print(f Лимит установлен: {self.___limit} руб.")
else:
print(f"Лимит должен быть больше нуля')
account = CreditAccount( Романов П. А.”, 5000b)
print(f"Текущий лимит: {account.limit} руб.") # Получение через геттер
#	Попытка установить корректное значение account.limit = 750ОО # Установка через сеттер # Попытка установить некорректное значение account.limit = -500о # Сеттер блокирует изменение
Объектно-ориентированное программирование 343
Вывод:
Текущий лимит: 50000 руб.
Лимит установлен: 75000 руб.
Лимит должен быть больше нуля
Пример 2. Изменение >ромкости в игре
Класс Gamevolume предназначен для управления громкостью звука в игре. Уровень громкости должен быть целым числом и не может быть меньше 0 и больше 100. Использование сеттера с декоратором ^volume.setter не разрешает установку недопустимого значения: class Gamevolume:
...Описывает управление громкостью в игре. II II II def___init__(self, volume: int):
"""Конструктор класса GameVolume.
Параметры:
volume: Громкость звука в игре.
II *1 II self.___volume = volume
#	Геттер ^property def volume(self) -> int: """Геттер. Возвращает значение приватного атрибута ______volume.
II II II return self.___volume
#	Сеттер gvolume.setter def volume(self, new_volume: int | float) -> None: """Сеттер. Устанавливает новое значение атрибута громкости ______volume.
Изменяет значение тольк” в том случае, если новое значение new_volume является целым числом в диапазоне от 0 до 100.
Параметры: new_volume: Новый уровень громкости звука. II II II
if isinstance(new_volume, int) and 0 <= new_volume <= 100: self.______volume = new_volume
print(f"Громкость изменена: {new_volume}") else:
print( Уровень громкости должен быть от 0 до 100 )
game_volume = GameVolume(7й)
Объектно-ориентированное программирование 344
#	Получение текущего значения громкости через геттер print(Е'Текущий уровень громкости: {game_volume.volume}’ )
if Установка корректного значения через сеттер game_volume.volume = 75
#	Установка некорректного значения через сеттер gamevolume.volume = 1и00
Вывод:
Текущий уровень громкости: 70
Громкость изменена: 75
Уровень громкости должен быть от 0 до 100
П	ример 3. Вычисление зарплаты сотрудника
Класс Employee описывает сотрудника и хранит приватную почасовую ставку___hourly_rate и отработанные часы hours_worked. При этом зарплата со-
трудника не хранится как атрибут, а вычисляется каждый раз при обращении через геттер с декоратором ^property monthly_salary. который использует приватный метод_____get_salary():
class Employee: ........Описывает сотрудника и вычисляет его зарплату на основе ставки."""
def __init_(self, name: str, hourly_rate: float, hours_worked: int):
..конструктор класса Employee.
Параметры:
name: Имя сотрудника.
hourly_rate: Почасовая ставка, hoursworked: Отработанные часы, tl fl II self.name = name self.hours_worked = hours_worked # Публичные часы self.__hourly_rate = hourly_rate ft Приватная ставка
def  get_salary(self) -> float: .......Возвращает зарплату на основе часовой ставки и к>личества часов. II II II
return self.__hourly_rate * self. hours_worked
^property def monthly_salary(self) -> float: """Геттер, возвращает зарплату, не храня её в атрибуте. II II II
# Читает приватный атрибут _hourLy_rate
return self.__get_salary()
Объектно-ориентированное программирование 345
@monthly_salагу.setter
def monthly_salary(self, rate: float) -> None: """Сеттер: Устанавливает новую почасовую ставку _______hoursrate,
если она является положительным числом.
Параметры rate: Нс ное значение почасовой ставки, tt tl fl
if rate > 0:
self._hourly_rate = rate
print(f"Ставка обновлена до {rate:.2f} руб/час. )
else:
print('Ставка должна быть больше нуля )
employeel = Employee("Ломоносов М. В", 80и, 160)
print(f Зарплата {employeel.name} (до): {employeel.monthly_salaryj ) employee].monthlysalary = 85o
print(f Зарплата {employeel.name} (после): {employeel.monthly_salary} )
Вывод:
Зарплата Ломоносов M. В (до): 128000 Ставка обновлена до 850.00 руб/час.
Зарплата Ломоносов М. В (после): 136000
Итоги
S Публичные атрибуты и методы не начинаются с подчеркиваний и доступны как внутри класса, так и вне его.
J Защищённые атрибуты и методы начинаются с одного подчеркивания, и считаются доступными только внутри класса и во всех его дочерних классах. Однако к ним можно обращаться напрямую и?вне класса.
Приватные атрибуты и методы начинаются с двух нижних подчеркиваний. и доступны только внутри класса. Прямой доступ к ним извне класса не рекомендуется и затруднён механизмом изменения имени этого атрибута.
J Геттер - это метод, позволяющий получить значение атрибута.
Сеттер - это метод, позволяющий установить или изменить значение атрибута.
Первый метод, который помечается декоратором ^property, становится геттером, а сеттер создается с помощью декоратора @имя_геттера.setter.
Объектно-ориентированное программирование 346
Задания для самопроверки
1.	Чем защищённые атрибуты отличаются от приватных?
2.	Создайте класс для банковского счёта BankAccount, который инициализируется защищённым атрибутом с именем владельца и приватным атрибутом с текущим балансом и начальным значением 0. Назовите атрибуты по своему усмотрению. Какие особенности именования атрибутов в данном случае следует учитывать?
3.	Создайте класс пользователя User, который инициализируется приватным атрибутом__age с возрастом. Напишите геттер get_age(), который возвращает значение атрибута_age Создайте объект класса User с произвольным возрастом. Получите значение атрибута_age с помощью геттера и выведите его на
экран.
4.	В классе User из предыдущего задания создайте сеттер set_age(self, newage), который изменяет значение атрибута_age на new _age только в том
случае, если new age находится в диапазоне от 0 до 100 включительно. В противном случае, выведите на экран строку "Возраст должен быть в диапазоне от 0 до 100" и не меняйте атрибут. Измените значение атрибута_age в ранее создан-
ном объекте с помощью сеттера сначала на число 18, затем на -21 и выведите его на экран.
5.	В классе User из заданий 3 и 4 перепишите геттер и сеттер с помощью декоратора (Sproperty. Создайте новый объект класса User с произвольным возрастом, измените его с помощью сеттера и выведите его на экран с помощью геттера.
53 НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
Наследование и полиморфизм - это ещё два основных принципа ООП. которые часто используются вместе. Наследование позволяет дочернему классу перенимать свойства и поведение родительского класса. А полиморфизм позволяет работать с объектами разных классов через единый интерфейс.
Наследование
Наследование - это механизм, который позволяет создавать новый класс на основе уже существующего. Это как в жизни: вы наследуете черты от своих
Объектно-ориентированное программирование 347
родителей, но при пом имеете и свои собственные, уникальные качества. В программировании это означает, что дочерний класс (потомок) получает все атрибуты и методы родительского класса (предка), а затем может добавлять новые или переопределять существующие.
Это ключевой принцип, позволяющий повторно использовать код. Вместо того чтобы писать один и тот же код для похожих классов, вы создаете базовый класс с общими характеристиками, а затем наследуете его.
Допустим, нам нужно создать классы для разных типов банковских счетов. У всех счетов есть общий набор атрибутов и методов: у них есть номер ас-count_number и баланс balance, который можно пополнить через метод deposit () или снять с него средства с помощью метода withdraw(). Тогда мы можем создать базовый класс счёта Account:
class Account:
def___init__(self, account_number: int, balance: int | float=0.0):
self.account_number = acccunt_number # номер счёта self.balance = balance # Баланс
def deposit(self, amount: int | float) -> Nene: self.balance += amount print(f"C4eT №{self.account_number) пополнен на {amount} руб.")
def withdraw(self, amount: int | float) -> bool: if self.balance >= amount: self.balance -= amount print(fd’Co счета №{self.account_number) снятг. {amount} руб. ) return True
рг1п1("Недостаточно средств ) return false
Давайте создадим объект этого класса и посмотрим на его работу:
асе = Ac count(134876134, 10_000)
асе.deposit(1000)
#	Вывод: Счет №134876134 пополнен но 1000 руб.
acc.withdraw(100)
#	Вывод: Со счета №134876134 снято 160 руб.
print(f Баланс счета {асе.account_number} {асе.balance) руб.')
#	Вывод: Баланс счёта 134876134: 10406 руб.
То есть мы можем создать счёт с определённым балансом, который можем пополнять и с которого можем снимать деньги. Это базовая функциональность практически любого счёта в банке.
Также в банке есть сберегательные счета. Они обладают всеми темп же
Объектно-ориентированное программирование 348
характеристиками, что и обычные счета, но также имеют процентную ставку и уникальный метод для начисления процентов. Чтобы не дублировать код, воспользуемся механизмом наследования и создадим класс SavingsAccount. который будет дочерним классом класса Account, после чего добавим в него специфичные для сберс! ательного счета атрибуты и методы.
Обычно мы не используем скобки при объявлении класса. Но если этот класс наследуется от другого, то в круглых скобках должно быть указано имя родительского класса:
class SavingsAccodnt(Accciint):
pass
Функция superf) и переопределение методов
При создании объектов класса SavingsAccount мы должны инициализировать все его атрибуты - и те, что принадлежат ему самому, и те, что пришли от родительского класса. Конечно, мы можем вручную прописать инициатизацию каждого общего атрибута, но вместо дублирования кода мы можем использовать функцию super().
Функция	super()
Описание	Обеспечивает доступ к методам родительского класса
Возвращаемое значение	Объект-посредник, обеспечивающий доступ к методам родительского класса
Эта функция позволяет обращаться к методам родительского класса, в том числе, магическим методам, поэтому она может вызвать конструктор родительского класса___init__() и инициализировать общие атрибуты:
class SavingsAccuunt(Account):
def__init__(self, account_number: int, balance: float=0.e):
super().__init (account number, balance)
При таком вызове любого метода родительского класса ему также надо передать все его аргументы, Например, для метода_____init__() - это все значения
атрибутов.
Вызов функции super().____init___() гарантирует, что родительский класс
будет инициализирован корректно, независимо от того, как изменится его конструктор в будущем. И если в класс Account будут добавлены новые атрибуты
Объектно-ориентированное программирование 349
(например, валюта currency), нам не придётся менять конструкторы всех его дочерних классов.
При этом функция super() не обязательно используется только для вызова метода___init__() Вы можете использовать её для вызова любого метода роди-
тельского класса, который вы хотите расширить или переопределить.
Например, если банк решит начислять дополнительный бонус при пополнении сберегательного счёта на сумму от 100000 рублей, то мы можем расширить логику метода deposit():
class SavingsAccount(Account):
def-__init__(self, account_number: int, balance: float=0.0):
super().__init_(account_number, balance)
def deposit(self, amount: int | float) -> None:
#	Вызываем родительский метод super().deposit(amount)
#	Добавляем новые действия
if amount > 100_000:
bonus = amount * 0.02 # Бонус 2% от суммы пополнения self.balance += bonus
print(f"Начислен бонус в размере -[bonus} руб. )
Здесь мы создаём новый метод deposit() с тем же именем, что и в родительском классе. Это называется переопределением метода. То есть дочерний класс может предоставлять свою собственную реализацию метода, который уже определён в его родительском классе.
Вызов функции super() .deposit(amount) передаёт управление родительскому классу, чтобы он увеличил баланс счёта. Это избавляет нас от необходимости повторять строку self .balance += amount. После того как родительский метод отработал, мы добавляем нашу новую, специфическую для класса Saving-sAccount логику: проверяем, превышает ли сумма пополнения 100000 и, если это так, начисляем дополнительный бонус.
Добавление новых методов и атрибутов в дочерний класс
Теперь, когда мы предоставили дочернему классу доступ к общим атрибутам и переопределили нужные нам методы, настало время добавить уникальные атрибуты и методы для этого класса.
Для класса сберегательного счёта SavingsAccount следует добавить уникальный атрибут процентной ставки interest_rate и метод начисления процентов add_interest():
Объектно-ориентированное программирование 350
class SavingsAccount(Acccunt): def ______init__(
self, accnuntnumber: int, balance: flcat=0.0, interest_rate: int«5 ):
super().__init__(account_nurnber, balance)
self.interest_rate = interest_rate А Процентная ставка
def deposit(self, amount: int | float) -> Nene: # Вызываем родительский метод super().deposit(amount)
#	Добавляем новые действия if amount > 100,000:
bonus = amount * 0.02 # Бонус 2% от суммы пополнения self.baiance += bonus print(f"Начислен бонус в размере {bonus} руб. )
def add_interest(self) -> None: interest = self.baiance * (self.interest_rate / 100) self.baiance += interest print(f"Начислены проценты: {interest} руб.")
Теперь давайте создадим новый сберегательный счёт и начислим на него проценты:
sav_acc = SavingsAccount(134876135, 10Э_000, 9) sav_acc.aad_interest()
#	Вывод: Начислены проценты: 9000.0 руб.
рг!пг(Г"Баланс счёта {sav_acc.account_number}: {sav_acc.balance) руб ) # Вывод: Баланс счёта 134876135: 10У000.& руб.
Также мы можем использовать метод родительского класса withdraw() для сня гия денег со счёта:
sav_acc.wit hdraw(J 00)
#	Вывод: Co счета №134876135 снято 100 руб.
И переопределённый метод deposit(). начисляющий бонус, если сумма пополнения больше 100 тысяч:
sav_асе.deposit(1000)
#	Счет №134876135 пополнен на 1000 руб. sav_acc.deposit(200_000)
#	Счет №134876135 пополнен на 200000 руб.
#	Начислен бонус в размере 4000.0 руб.
Так как в переопределённом методе был вызван метод deposit () родительского класса Account, то здесь сначала выполняется пополнение счёта, а затем Объектно-ориентированное программирование 351
проверяется сумма пополнения для начисления бонусов.
Множественное наследование
Класс может наследоваться не только от одного, но и от нескольких родительских классов. Это называется множественным наследованием.
Допустим, у нас есть класс BluetoothDevice для подключения беспроводного устройства через Bluetooth и класс WiFiDevi.ce для подключения через WiFi:
class BluetoothDevice: def connect_bluetooth(self) -> None: # Логика подключения
print( Подключено через Bluetooth’)
def turn_or>(self) -> None:
рг1п1("Включение Bluet c: th-модуля")
class WiFiDevice:
def connect_vvifi(self) -> Nene:
# Логика подключения
print( Подключен через Wi-Fi')
det turn_on(self) -> None:
print('Включение Wi-Fi-модуля')
Умная колонка может быть подключена через Bluetooth или Wi-Fi, и если мы разрабатываем класс Smartspeaker для управления этой колонкой, то вместо того, чтобы писать все функции ещё раз, мы можем унаследовать нужные функции от двух базовых классов. BluetoothDevice и WiFiDevice. В таком случае в скобках после имени дочернего класса через запятую перечисляются имена всех родительских классов:
class SmartSpeaker(Blueti athDevice, WiFiDevice): def play_music(self) -> None:
prinr( Рсспроизведение музыки. )
Теперь все объекты класса Smartspeaker могут использовать как собственные методы, так и имеют доступ ко всем методам обоих родителей:
my_speaker = SmartSpeaker()
my_speaker.connect_bluetooth() # Метод от BLuetoothDevice
it Вывод: Подключено через BLuetooth
my_speaker.connect_wifi() # Метод от WiFiDevice
# Вывод: Подключено через Wi-Fi
my_speaker.play_music() it Собственный метод
it Вывод: Воспроизведение музыки...
Объектно-ориентированное программирование 352
Порядок разрешения методов
Множественное наследование может создать проблему ромба. Она возникает, когда два родительских класса наследуют от общею предка. Пусть у классов Bl uetoothDevice и WiFiDevice есть общин родительский класс устройства Device При этом у класса Device, как и у обоих его дочерних классов, есть метод включения turn_on():
class Device:
def turn_or(self) -> None:
print("Включение устройства")
Тогда возникает вопрос: какой именно из методов turn_on() будет вызван у объекта класса Smartspeaker?
Рисунок 22. Структура множественного наследования в виде ромба
Чтобы избежать путаницы и обеспечить предсказуемое поведение, в Python существует порядок разрешения методов (англ, method resolution order или MRO). Это строго определённый порядок, в котором Python ищет методы и атрибуты в иерархии наследования.
Для обеспечения порядка Python использует специальный алгоритм, который гарантирует, что поиск всегда будет идти от самого специфического класса (дочернего) к самому общему (родительскому), а также сохранится порядок, в котором были указаны родительские классы.
Поэтому в нашем случае будет вызван метод turn_on() класса Blue-toothDevice, так как он указан первым:
Объектно-ориентированное программирование 353
my_speaker = SmartSpeaker()
my_speaker.turn_on()
#	Вывод: Включение ВLuetootn-модуля
To есть если искомый метод есть или переопределён в классе объекта (дочернем классе), то будет использоваться он, иначе поиск будет вестись выше. Тогда для нашего примера с умной колонкой порядок будет следующий: Smartspeaker -* BluetoothDevice -> WiFiDevice -> Device -> object. В конце поиск будет вестись во встроенном базовом классе object, от которого неявно наследуют все остальные классы.
Полиморфизм
Полиморфизм - это способность одного и того же метода выполнять разные действия в зависимости от того, на каком объекте он был вызван. Это позволяет нам писать более универсальный и гибкий код.
Представьте, что вы создаете программу, которая обрабатывает заказы в интернет-магазине. У вас есть разные типы товаров: обычные товары, цифровые товары и подарочные сертифггкаты. Каждый из них нужно обрабатывать по-разному, но в то же время все они являются товарами, поэтому создадим базовый класс товара Product с методом обработки заказа process_order().
class Product:
def___init_(self, name: str, price: int):
self.name = name self.price = price
def process_order(self) -> None:
print(f"Обработка заказа товара \"{self.name}\"“)
Теперь создадим на его основе два дочерних класса: DigitalProduct для цифровых товаров и Giftcertificate для подарочных сертификатов, которые будут переопределять метод process_order() для своей уникальной логики.
class DigitalProduct(Product):
def process_order(self) -> None:
print(f Отправка ссылки на скачивание товара \"{self.name}\" )
class GiftCertificate(Product):
def process_order(self) -> None:
print(f"Отправка кода подар; >чногс сертификата \"{self.name}\"’)
Теперь мы можем создать список, который содержит объекты разных классов. и пройтись по нему в цикле, вызывая один и тот же метод process_order().
Объектно-ориентированное программирование 354
products = [
Product('Футболка , 500),
DigitaJProduct("Электронная книга', 300),
Giftcertificate( Сертификат на 1000 руб. , 1000)
]
for item in products:
item.process_order()
#	Вывод: Обработка заказа товара "Футболка"
#	Вывод: Отправка ссылки на скачивание товара "Электронная книга"
#	Вывод: Отправка кода подарочного сертификата "Сертификат на 1080 руб."
Как вы видите, одна и та же команда item.process_order() приводит к разным результатам, так как Pyihon вызывает соответствующую реализацию метода для каждого объекта. В этом и заключается суть полиморфизма.
Полиморфизм позволяет писать код, который работает с разными типами объектов. Если в будущем вы добавите новый класс товара, например подписку Subscription, вам не придется менять существующий код, который обрабатывает заказы. Достаточно будет создать новый класс, унаследовать его от Product и переопределить метод process_order().
Другими словами, полиморфизм позволяет создать универсальный интерфейс для работы с различными объектами, поэтому нам не нужно писать множество условных операторов if/elif/else. чтобы определить, какой метод вызывать.
Примеры
Пример 1. Создание отчётов
Класс StandardReport описывает стандартный отчёт в виде строки, который создаёт метод generate() Класс PDF Report наследуется от класса Report, то есть он наследует атрибут title для заголовка и атрибут data для данных, но переопределяет метод generate() и форматирует строку с отчётом для представления его в PDF:
class StandardReport:
..Базовый класс. Описывает стандартный отчет в виде строки.”""
def___init__(self, title: str, data: diet):
"""К нструктор класса Report.
Параметры: title: Заголовок, data: Данные.
Объектно-ориентированное программирование 355
self.title = title self.data = data
def generate(self) -> str:
..Возвращает строку с отчётом в самом простом виде.....
return f"{self.title}: {self.data}"
class PDFReport(StandardReport):
...Описывает стандартный отчёт в PDF-формате."""
def generate(self) -> str:
.................возвращает строку с отчётом для представления его в PDF."" detail_llnes = [f'{key}: {value} for key, value in self.data.items()]
content = \n\t .join(detail_lines)
return f"=== {self.title} (PDF) ===\n\t{content}
#	Создание объектов разных типов
data = {"Продажи'': 150008, "Расходы": 450Й8, "Прибыль": 105000}
title = "Ежемесячный финансовый отчёт1
st_report = StandardReport(title, data) pd-f_report = PDFR?port(title, data)
#	Использование полиморфизма: вызов одного метода для разных объектов
	for rep in [st_report, pdf_report]:
print (rep. gener'ateO)
Вывод:
Ежемесячный финансовый отчёт: {'Продажи': 150000, 'Расходы': 45000, 'Прибыль': 105000}
=== Ежемесячный финансовый отчёт (PDF) ===
Продажи: 150000
Расходы: 45000
Прибыль: 105000
Пример 2. Финансовые транзакции
Базовый класс Transaction описывает финансовые транзакции в общем виде. Два его дочерних класса DebitTransaction и Credit!ransaction переопределяют общий метод get_description() для описания списания или зачисления средств: class Transaction:
..Базовый класс. Описывает все финансовые транзакции."""
def____init__(self, amount: float, date: str):
Объектно-ориентированное программирование 356
...Конструктор класса Transaction.
Параметры:
amount: Сумма транзакции.
date: Дата совершения транзакции.
II II II
self.amount = amount
self.date = date
def get_description(self) -> str:
"""Метод для описания конкретного типа транзакции.
Возвращает:
Строка с общим описанием.
II II II
return f'Транзакция от {self.date} на сумму {self.amount} руб."
class DebitTransactinn(Transaction): """Описывает дебетовую транзакцию (списание)."""
def get_description(self) -> str:
"""Возвращает описание списания."""
return f’CnncaHne средств ({self.date}): -{self.amount} руб."
class CreditTransaction(Transaction): ....... Описывает кредитную транзакцию (зачисление)."""
def get_descriptL-n(self) -> str:
"""Возвращает описание зачисления."""
return f"3a4HcneHne средств ({self.date}): +{self.amount} руб."
# Создание транзакций разных классов transactions = [
DebitTransaction(1500.00, "2025-10-25"),
CreditTransaction(50000.00, "2025-10-25"),
DebitTransaction 500.50, “2025-10-26"), ] for trans in transactions:
print(trans.get_descriptioa())
Вывод:
Списание средств (2025-10-25): -1500.00 руб.
Зачисление средств (2025-10-25): +50000.00 руб.
Списание средств (2025-10-26): -500.50 руб.
Объектно-ориентированное программирование 357
Пример 3. Расчёт июювои стоимости с учётом скидки и НДС
Класс Invoice, описывающий счёт за товар, является дочерним классом класса TaxCalculator, описывающим расчёт налога, и класса DiscountApplier, описывающим применение скидки. Он рассчитывает итоговую сумму с учётом скидки и налога, расчёт которых унаследован от обоих этих классов:
class TaxCalculator:
... Базовый класс. Описывает расчет налога."""
def calculate_tax(self, amount: float, tax_rate: int) -> float: """Рассчитывает сумму налога для заданной суммы.
Параметры:
amount: Сумма без налога.
taxrate: Налоговая ставка в процентах.
Возвращает:
Сумма налога.
It ft II
return amount * tax_rate / 100
class DiscountApplier:
.. Базовый класс. Описывает применение скидки."""
def apply_discount(self, amount: float, discount: int) -> float: """Применяет скидку к сумме.
Параметры:
amount: Исходная сумма.
discount: Скидка в процентах.
й.»звращает:
Сумма после применения скидки. Н М II
discount_amjunt = amount * (discount / 10и) return amount - discount_amount
class Invoice(TaxCalculator, TiscountApplier): ......Описывает выставление счёта с учётом налога и скидки......
def finalize_total(self, raw_amount: float, discount: int, tax_rate: int) -> float:
"""Рассчитывает итоговую сумму: сначала скидка, потом налог.
Параметры: raw_amount: Исходная сумма до всех расчетов, discount: Скидка в процентах.
Объект но-ориентированное программирование 358
руб.")
tax_rate: Налоговая ставка в процентах.
Возвращает:
Финальная сумма к оплате (float). It II и
#	Применяем скидку (от DiscountAppLier)
amount_after_discount = self. apply_discount(raw_a'nountJ, discount)
#	Рассчитываем налог (от JaxCaLcuLator) tax = self.calculate_tax(amount_after_discount, tax_rate)
final_total = amount_after_discount + tax
print(f'Исходная сумма: {raw_amount} руб. )
print(f Сумма после скидки ({discount} %): {amount_after_discount}
print(f Налог ({tax_rate} %): {tax} руб.") return final_total
invoice = Tnvoice() final_price = invoice.finalize_total(
raw_ amount=l000, discount=10, tax_rate=20
)
print(f Сумма к оплате: {final_price} руб. )
Вывод:
Исходная сумма: 1000 руб.
Сумма после скидки (10 %): 900.0 руб.
Налог (20 %): 180.0 руб.
Сумма к оплате: 1080.0 руб.
Итог и
✓ Наследование - это механизм, который позволяет создавать новый класс на основе уже существующего.
J Если класс наследуется от другого, то имя родительского класса указывается в круглых скобках после имени дочернего класса.
Функция super() позволяет обращаться к методам родительского класса. Переопределение метода - это возможность дочернего класса предоставить свою собственную реализацию метода, существующего в родительском классе.
J Класс может наследоваться не только от одного, но и от нескольких родительских классов. Это называется множественным наследованием.
Объектно-ориентированное программирование 359
J Если класс наследуется от двух родительских классов, у которых есть общий родительский класс, то поиск метода ведётся от дочернего класса к самому общему (базовому классу object, от которого неявно наследуются все остальные классы). При этом сохраняется порядок, в котором были указаны родительские классы.
J Полиморфизм - это способность одного и того же метода выполнять разные действия в зависимости от того, на каком объекте он был вызван.
Задания для самопроверки
1.	Опишите суть проблемы ромба, которая может возникнуть при множественном наследовании. Как она решается в Python?
2.	Создайте классы для машины Саг, велосипеда Bicycle и лодки Boat. Все зри класса должны иметь метод move():
о Метод Car.move() выводит строку "Едет по дороге...
о Метод Bicycle. move() выводит строку "Едет по горной тропе...", о Метод =;oat.move() выводит строку "Плывет по реке...".
Создайте список, содержащий по одному объекту каждого класса. Пройдитесь по списку в цикле for и вызовите move() для каждого объекта, демонстрируя полиморфизм.
3.	Для классов Саг, Bicycle и Boat из предыдущего задания создайте родительский класс для гранспорга Vehicle, в котором придумайте минимум один общий метод для этих классов Создайте объект класса Boat и вызовите метод, унаследованный от класса Vehicle.
4.	Создайте базовый класс Employee с методом log_work(), который выводит строку "Сотрудник приступил к работе". Создайте его дочерний класс Manager и переопределите в нём метод log_work(). Сначала он должен вызвать родительский метод (с помощью функции super()), а затем вывести дополнительную строку "В 12:00 будет совещание". Создайте объект класса Manager и вызовите метод log_work().
5.	Создайте два базовых класса: Walker для ходьбы с методом walk(), который выводит строку "Идет по земле" и Swimmer для плавания с методом swim(), который выводит строку "Плынет воде" Создайте дочерний класс Amphibian, описывающий земноводных, который наследуется от классов Walker и Swimmer и инициализируется атрибутом с видом specie. Создайте объект класса Amphibian
Объектно-ориентированное программирование 360
и вызовите методы walk() и swim().
5.4 ПОЛУЧЕНИЕ ДАННЫХ ОБ ОБЪЕКТЕ
В процессе разработки часто возникает необходимость получить информацию о созданном объекте: его атрибутах, методах или просто читабельное строковое представление. Для этого в Python существуют специальные инструменты.
Магические методы str () и rept _()
Когда мы работали с объектами встроенных типов данных, то достаточно легко могли выводить их на экран Но если мы используем функцию print() с объектом собственного класса, то увидим лишь название класса и адрес этою объекта в памяти компьютера.
Давайте создадим простой класс Student для работы со студентами и выведем на экран объект этого класса:
class Student:
def__init_(self, name: str, email: str):
self.name = name self.email = email
student] = Student(’Романова E.A.', "best_of_the_best@example.ru ) print(studentl)
tt Вывод: <_main_.Student object at 8x...>
Здесь вы видите имя__main__, означающее, что данный класс был опреде-
лен в запускаемом файле. А также название класса объекта и адрес этого объекта в шестнадцатеричной системе счисления.
Но с помощью специальных методов______str _() и _ герг__() мы можем
контролировать строковое представление объекта, которое вызывается, например. функцией print():
о Метод____str__() предназначен для ноль зователя, поэтому он должен во з-
вращать читаемую и понятную человеку строку. Этот метод вызывает уже знакомая нам функция str(), которая для встроенных типов данных просто преобразует значение объекта в строку.
о Метод____герг__() (от англ, representation - представление) предназначен
для разработчика, поэтому он должен возвращать формальное описание объекта, которое однозначно описывает его и может использоваться для
Объектно-ориентированное программирование 361
восстановления объекта. Этот метод вызывает функция герг().
Функция
Описание
Параметры
Возвращаемое значение
repr(object)
Возвращает формальное строковое представление объекта object
• object - объект, чьё формальное строковое представление следует получить
Формальное строковое представление объекта
Давайте добавим методы_______str___() и___герг___() в наш класс Student:
class Student:
def___init___(self, name: str, email: str):
self.name = name self.email = email
def __str___(self) -> str:
return f"Студент: {self.name}, электронная почта: {self.email)
def __rep г4_(self) -> str:
return f"Student(name='{self.name}', email='{self.email}')"
Теперь при выводе объекта этого класса на экран функция print() будет вызывать метод_____str___():
student2 = Student("Рюрикович И.В.", "iv_the_terr@example.ru ) print(studentl)
#	Вывод: Студент: Рюрикович И.В., электронная почта: iv_the_terr@exampLe.ru
Если бы мы его не определили, то функция print() вызывала бы метод __герг___() Также мы можем вызвать этот метод с помощью функции герг():
print(repr(student2))
#	Вывод: Student(name-PmpuKo6u4 H.B.'j emaiL='iv_the_terr@exampLe.ru')
Восстановление объекта и функция eval()
Если строка, возвращаемая методом __герг__() является синтаксически
корректным выражением на Python, которое при выполнении создаст такой же объект, то мы можем восстановить этот объект с помощью функции eval ().
Объектно-ориентированное программирование 362
Функция
Описание
Параметры
Возвращаемое значение
eval(expression)
Возвращает результат выполнения строки expression
• expression - строка, которая должна быть выполнена как команда на Python
Результат выполнения строки
Функция eval() принимает строку и выполняет ее как выражение на Python. Это позволяет превратить строковое представление объекта, полученное с помощью____герг___(), обратно в сам объект.
Давайте вернемся к нашему классу Student и восстановим объект на основе его сгрокового представления в методе____герг__():
student3 = Student("Романов А.М. , "tne_second(q)example.ru' )
restored_student = eval(repr(student3))
print(f восстановленный эбъект: {restored_student}1)
#	Вывод: Восстановленный объект: Студент: Романов A.M.j электронная почта: the_second@exampLe.ru
Здесь функция eval() выполняет код, который был возвращен методом герг__(), поэтому это создаёт новый объект класса Student, полностью иден-
тичный исходному, а функция print () выводит на экран его строковое представление, возвращаемое методом_____str__().
На самом деле функция eval() может не только восстанавливать обьекты, но и выполнять любые корректные выражения на языке Python:
print(eva]("2 + 2'))
#	Вывод: 4
print(eval("round(3.1415, 2) ))
#	Вывод: 3.14
Однако при использовании этой функции следует быть осторожным, так как она выполняет любой код, написанный в строке. И если вы используете её с данными, полученными из ненадежных источников, например, от пользователя или из интернета, это может привести к проблемам безопасности.
Список атрибутов и методов объекта
Для любого объекта можно получить список всех его атрибутов и методов с помощью функции dir().
Объектно-ориентированное программирование 363
Функция
Описание
Параметры
Возвращаемое значение
dir(object)
Возвращает список атрибутов и методов объекта
• object - объект, для которого нужно получить список атрибутов и методов
Список атрибутов и методов объекта
Например, у нас есть класс Magicitem, описывающий магический предмет в игре:
class MagicTtem:
def __init__(self, name: str, charge: int, casting_time: int = 5):
self.name = name # Название self.charge = charge # Заряд
self.casting_time = casting_time # Время для использования
def use(self) -> None:
# Использует предмет, только если есть заряд
if self.charge > 0:
self.charge -= 1
print(f '{self.name} использсган! Осталось: {self.charge} ) else:
print(f"{self.name} разряжен. He<бхлдима перезарядка")
Давайте создадим экземпляр класса Magicitem и передадим его функции dir():
artifact = Magicltem( 'Волшебный посох', 3)
print(dir(artifact))
В результате на экране вы увидите достаточно длинный список всех атрибутов и методов этого объекта:
['___class_', '__delattr__', ' diet '________dir__', '__doc__', '__eq__',
'__firstlineno__',	'__format__', ’___ge_', '__getattribute____', '_getstate__',
'__gt__', '_hash	', '___init__', '_init_subclass___', '_le____', '__It_', '__module____________________________________________________________________________‘, '_ne_',_'_new_',	'_reduce_', '_reduce_ex_', '_repr_',
'__setattr__', '___sizeof_', '__static_attributes__', '___str__', '__subclasshook_________________________________________________________________', '_weakref_', 'casting_time', 'charge', 'name', 'use']
Этот список содержит все магические методы этого объекта, унаследованные от базового класса object, а также все а<рибуты и методы, принадлежащие объекту artifact
Объектно-ориентированное про!раммирование 364
Объект object является базовым классом, от которого неявно наследуются все остальные классы в Python. Он определяет базовую реализацию для всех магических методов и когда вы создаете новый класс, вы не пишите все эти методы с нуля, а просто переопределяете ту реализацию, которую предоставил object Также object гарантирует, что любой объект в Python (будет ли это число, строка, список или ваш собственный класс) будет иметь общие методы, такие как___str__() или__герг__()
Поэтому функция dir() может быть полезна при работе с незнакомым кодом, поскольку позволяет быстро исследовать новый объект. Например, если вы получаете некий объект из функции и не знаете, какие методы он поддерживает, вызов dir() немедленно даст вам список всех доступных команд.
Для собственных классов Python предоставляет способ переопределить результат вызова функции dir() с помощью магического метода___________dir _(). Он
позволяет классу явно контролировать, какие имена (атрибуты и методы) будут включены в список, возвращаемый функцией dir(). Единственным ограничением является го. что метод____dir___() должен возвращать список строк.
Теперь переопределим метод_____dir____() в классе Magicltem таким образом,
чтобы он возвращал только созданные нами атрибуты и методы:
class Magicltem:
def___init_(self, name: str, charge: str, casting_time: int = 5):
self.name = name # Название self.charge = charge # Заряд
self.casting_time = casting_time # Время для использования
def use(self) -> None:
"""Использует предмет, толькс если есть заряд.
if self.charge > 0:
self.charge -= 1
print(f"{self.name) использован! Осталось: {self.charge) ) else:
print(f"{self.name) разряжен. Необходима перезарядка )
def __dir__(self) -> list[str]:
return ["name1, "charge", "casting_time", ’use"]
Объектно-ориентированное программирование 365
В результате функция dir() вернёт именно тот список, который возвращает метод____dir___():
artifact = Magicltem( юлшебная палочка", 5, 3) print(dir(artifact))
#	Вывод: ['casting_time‘, 'charge', 'name', 'use']
Это не влияет на доступ к атрибутам - объект по-прежнему содержит все свои данные.
Функции type и isinstance()
Как и при работе со встроенными типами данных, для объектов собственных классов мы также можем вызвать функцию type() для определения к какому классу принадлежит объект и функцию isinstance() для проверки объекта на принадлежность к какому-то классу.
Допустим, у нас есть класс для точки Dot, которая определяется координатами х и у. Функция type() для объекта этого класса вернёт строку cclass '__main___. Dot’>, а функция isinstance(), в зависимости от проверяемого
класса, вернёт True или False: class Dot: def ___________init__(self, x: int | float, y: int | float):
self.x = x self.у = у
dotl = Dot(3, 5) print(type(dotl)) # Вывод: <cLass '_rnain_.Dot‘>
print(isinstance(dotl, int)) # Вывод: False print(isinstance(dotl, Dot)) # Вывод: True
Основное различие между функциями type() и isinstance() проявляется при работе с наследованными классами, так как объект дочернего класса, по сути, является также и объектом родительского класса.
Давайте создадим класс для цветной точки ColorDot, который будет наследоваться от класса Dot: class СоLorDot(Dot):
def _init__(self, x: int | float, y: int | float, color: str-):
super().__init__(x, y)
self.color = color
Объект но-ориентированное программирование 366
Функция isinstance() учитывает иерархию наследования и вернет True, если объект является экземпляром указанного класса (ColorDot) или любого из его родительских классов (Dot):
colored_dot = ColorDot(l, 2, "Красный')
print(isinstance(colored_dot, ColorDot))
#	Вывод: True
print(isinstance(colored_dot, Dot))
#	Вывод: True
Также функция isinstance() может принимать кортеж классов вторым аргументом. Она вернет True, если объект принадлежит хотя бы одному из них: print(isinstance(colored_ciot, (int, Dot, float))) # Вывод: True
Функция type(), в свою очередь, просто возвращает класс объекта и не учитывает наследование классов:
print(type(coloreddot))
#	Вывод: <cLass '_та-in_.ColorDot' >
А грибу г____diet___
Мы уже говорили, что у каждого объекта Python есть специальный атрибут __diet__. который является словарем. Он хранит все атрибуты экземпляра, определенные для этого объекта, и их значения.
Допустим, у нас есть класс Databaseconnection для подключения к базе данных, в таком случае атрибут_____diet__позволяет получить словарь с атрибу-
тами экземпляра класса: имя базы данных db_name и статус is_active:
class HatabaseConnection: def _____ir.it_(self, db_name: str):
self.dbname = db_name self._is_active = False
dbl = DatabaseConnection( 'my_db'‘)
print (dbl._diet_)
#	Вывод: {'db_name‘: 'ту_дЬ‘> '_is_active': FaLse}
Примеры
Пример 1. Описание книги в библиотеке
Класс Book описывает книгу в библиотеке. Красивое и читабельное описание объекта обеспечивает метод_____str___(), а формальное, которое может быть
Объектно-ориентированное программирование 367
использовано для восстановления функцией eval(), обеспечивает метод __герг___(): class Book: """Описывает книгу в библиотеке................
def____init_(self, title: str, author: str, year: int):
..конструктор класса Book.
Параметры: title: Название книги, author: Автор, year: Год издания. If fl II self.title = title self.author = author self.year = year def ___str__(self) -> str:
"""Возвращает красивое, читаемое описание объекта.
Возеращает: Строка в формате "Название (Автор, Год)". II М II
return f‘Книга: ' {self.title)' (Автор: {self.author}, Год: {self .year})*'
def ___repr_(self) -> str:
...Возвращает формальное, восстанавливаемое представление объекта.
Возъращает:
Строка, пригодная для повторного создания объекта через eval(). If М II
return f"Book(title='{self.title}', author='{self.author)', year={self.year})"
book = Fook( "Война и мир , "Л. Н. Толстой", 1£б9)
#	Получаем описание объекта print(Г'Пользсьательское мисание книги: {book}’’) print (РОтисание книги для разработчика: {repr(oook))’)
#	восстанавливает книгу с помощью функции evaL() res+o-'ed_booK = eval(repr(b>ok)) print (Р Исходная и восстановленная книги совпадают: {str(restored_book) == str(book)} )
Объектно-ориентированное программирование 368
Вывод:
Пользовательское описание книги: Книга: 'Война и мир1 (Автор: Л. Н. Толстой, Год: 1869)
Описание книги для разработчика: Book(title='Война и мир', author='/l. Н. Толстой', уеаг=1869)
Исходная и восстановленная книги совпадают: True
Пример 2. Атрибуты обьекта класса соединения с ба той данных
Класс Databaseconnection описывает соединение с базой данных и инициализируется атрибутами с адресом сервера host, именем пользователя user и статусом соединения is_active. При использовании функции dir() имя пользователя скрывается из списка атрибутов объекта, так как атрибут user не указывается в списке, возвращаемом переопределённым методом _ dir _(), но полный словарь со всеми атрибутами их значениями возвращает атрибут _ diet__________:
class Databaseconnection:
...Описыьэет соединение с базой данных."""
def___init__(self, host: str, user: str, is_active: cool = False):
"""Конструктор класса DataoaseConnection.
Параметры:
host: Адрес сервера.
user: Имя пользователя.
is_active: Статус соединения.
tt *1 II
self.host = host self.user = user self.is_active = isactive def __dir___(self):
"""Переопределяет результат функции dir(), озврашая только нужные атрибуты. It II II
# Возвращаем список строк с именами, которые хотим показать return ["host', "is_active"]
conn = natabaseConnection( localhost , admin' )
if Получаем словарь всех атрибутов, определённых для этого обьекта print(f"CnHCOK атрибутов экземпляра (функция dir()): {dir(conn)}”) print(f"Словарь с атрибутами экземпляра (атрибут___dict__): (conn.__diet__}")
Вывод:
Список атрибутов экземпляра (функция dir()): ['host', 'is_active']
Словарь с атрибутами экземпляра (атрибут ______diet___): {'host': 'iocalhost',
'user': 'admin', 'is_active': False}
Объектно-ориентированное программирование 369
Пример 3. Квадраты и прямоугольники
В системе решения задач прямоугольник представлен классом Rectangle, от которого наследуется класс для квадрата Square. Поэтому для квадрата функция isinstance() возвращает True как при сравнении с классом Rectangle, так и с классом Square:
class Rectangle:
...Базовый класс. Описывает прямоугольник."""
def __init__(self, width: int, lenght: int):
"""Конструктор класса Rectangle.
Параметры:
width: Ширина прямоугольника, length: Длина прямоугольника, ft ft fl
self.width = width
self.lenght = lenght
def get_area(self) -> int:
"”"Eэзвращает площадь прямоугольника.’..
return self.width * self.lenght
class Square(Rectangle): .......Описывает квадрат....
def __init__(self, side: int):
"""Конструктор класса Square.
Параметры:
side: Сторона квадрата, ft ft It
super().__init__(side, side)
rect = Rectangle(5, 10)
sqr = Square(5)
print (f Класс прямоугольника: {type(rect))")
print(f"Knacc квадрата: (type(sqr)}')
print(f"Квадрат принадлежит (наследуется) классу Rectangle: {isinstance(sqr, Rectangle)}")
print(f"Квадрат принадлежит классу Square: {isinstance(sqr, Square)} )
Вывод:
Класс прямоугольника: <class '___main__.Rectangle'>
Класс квадрата: cclass ’__main___.Square’>
Квадрат принадлежит (наследуется) классу Rectangle: True
Объектно-ориентированное про1раммирование 370
Квадрат принадлежит классу Square: True
Итог и
Метод___str__() предназначен для пользователя, поэтому он должен воз-
вращать читаемую и понятную человеку строку. Этот метод вызывает функция str(),
J Метод___герг__() предназначен для разработчика, поэтому он должен
возвращать формальное описание объекта, которое однозначно его идентифицирует и может использоваться для восстановления объекта. Этот метод вызывает функция герг().
J Функция eval() возвращает результат выполнения переданной ей строки как обычного выражения на Python. Она может восстановить объект из строки, возвращаемой методом_____герг_().
J Функция di г() во звращает список всех атрибутов и методов объекта.
J Функция type() возвращает класс, к которому принадлежит объект.
J Функция isinstance() проверяет объект на принадлежность к какому-то классу.
J Атрибут___diet__хранит все атрибуты объекта и их значения в виде сло-
варя.
Задания для самопроверки
1.	Если класс имеет методы_str_() и_герг__(), то какой из них будет
вызван функцией print()?
2.	Почему даже для только что созданного класса, в котором нс определены никакие методы, функция dir() возвращает длинный список магических методов?
3.	Создайте класс для даты Date с атрибутами day для дня, month для месяца и year для года. Реализуйте методы_str_() и_герг_() для этого класса. Со-
здайте экземпляр этого класса и выведите на экран оба его строковых представлений.
4.	Создайте класс для секретного предмета Secretitem с публичным атрибутом name для имени и приватным атрибутом__code для кода. Переопределите
метод__dir___() так, чтобы он возвращал список, содержащий только публич-
ный атрибут name. Создайте объект класса Sec ret Item и выведите на экран вызов
Объектно-ориентированное программирование 371
функции dir() для этого объекта.
5.	Создайте родительский класс для мебели Furniture и его дочерний класс для стульев Chair. Придумайте минимум один публичный атрибут для каждого из классов. Сделайте гак. чтобы класс Chair при инициализации наследовал все атрибуты класса Furniture Создайте экземпляр класса Chair и проверьте, принадлежит ли он классу Furniture.
5.5 АТРИБУТЫ И МЕТОДЫ КЛАССА. СТАТИЧЕСКИЕ МЕТОДЫ
До сих пор мы говорили об атрибутах экземпляра класса, которые принадлежат конкретному объекту. Однако в Python представлены, в том числе, и атрибуты класса, которые принадлежат самому классу и являются общими для всех его экземпляров.
Чтобы понять разницу, давайте рассмотрим пример с классом Parcel, описывающим посылку в курьерской службе. У такого класса могут быть следующие атрибуты:
о Атрибуты экземпляра класса - это уникальные характеристики конкретной посылки. Например, weight для веса или address для адреса доставки. У каждой посылки свой вес и свой адрес.
о Атрибуты класса - это характеристики, которые одинаковы для всех посылок. обрабатываемых этой службой. Например, carrier для названия курьерской компании. Это свойство не меняется от посылки к посылке.
Атрибуты класса определяются внутри тела класса, но вне любого метода. К ним можно получить доступ как через сам класс, так и через любой его объект.
class Parcel:
# Атрибут класса
carrier = "МегаБыстраяДоставка"
def __init_(self, weight: int | float, address: str):
# Атрибуты экземпляра
self.weight = weight
self.address = address
Теперь давайте создадим два объекта этого класса и получим значение атрибута carrier с названием курьерской службы для обоих:
parcell = Parcel(5, "ул. Ленина, д. 10")
parce!2 = Рагсе1(2, "пр. Мира, д. 25 )
Объектно-ориентированное программирование 372
print(f"Посылку доставит: {parcell.carrier}")
#	Вывод: Посылку доставит: МегаЬыстраяДоставка
print(-Г'Посылку доставит: {parcel2.carrier} )
#	Вывод: Посылку доставит: МегаЬыстраяДоставка
Атрибут carrier является атрибутом класса, поэтому он общий для всех созданных объектов, и мы можем получить его через имя класса:
рг1пт(Г"Курьерская служба: {Parcel.carrier}”)
#	Вывод: Курьерская служба. МегаЬыстраяДоставка
Обратите внимание, что если мы не создаём новый экземпляр класса, то при обращении к атрибутам и методам класса круглые скобки после имени класса необязательны.
Атрибуты класса могут быть полезны в следующих случаях:
о Хранение констант - если у вас есть какое-то значение, которое никогда не меняется (например, максимальный вес посылки), его можно хранить как атрибут класса. Это делает код более читабельным, гак как сразу видно, к какой логической i руппе относится константа.
о Счётчики - атрибуты класса могут использоваться для подсчета количества созданных экземпляров. Например, каждый раз, когда создается новый объект-посылка, мы можем увеличивать значение атрибута класса, чтобы знать общее количество отправлений.
о Значения по умолчанию - если атрибут должен иметь одинаковое значение для всех объектов, но может быть изменен в будущем, его также удобно хранить как атрибут класса.
Изменение атрибута класса
При работе с атрибутами класса следует понимать, как происходит их изменение. Если вы изменяете атрибут класса через сам класс, это изменение отразится на всех экземплярах:
Parcel.carrier = "Скорость Без Границ"
print(f"Tenepb посылку доставит: {parcell.carrier}")
#	Вывод: Теперь посылку доставит: Скорость Без Границ
Однако, если вы изменяете атрибут класса через объект, вы не измените сам атрибут класса. Вместо этого вы создадите новый атрибут экземпляра с тем же именем, который скроет атрибут класса:
parcell.carrier = "Блиц"
print(Г'Посылку доставит: {parcell.carrier}")
Объектно-ориентированное программирование 373
#	Вывод: Посылку доставит: Блии
При этом у других объектов и у самого класса Parcel значение атрибута класса carrier осталось без изменений:
print(РПосылку доставит: {parcel2. carrier}’1)
#	Вывод: Посылку доставит: Скорость Без Границ
Поэтому, если вы хотите изменить атрибут класса для всех экземпляров, делайте это через имя класса, а не через его объекты.
Методы класса
Как и атрибуты, которые могут быть как у объекта, так и класса в целом, так и методы могут относиться к классу в целом, а не к его экземплярам. Такие методы называются методами класса и предназначены для решения задач, которые логически связаны с самим классом, а не с конкретными объектами, поэтому они могут работать только с атрибутами класса.
Метод класса объявляется с помощью декоратора @classmethod, а в качестве первого ар]умента принимают ссылку па сам класс, который принято обозначать cis, а не ссылку на объект self.
Давайте рассмотрим пример с классом Item для товаров, добавляемых в корзину пользователя в интернет-магазине. У этого класса есть атрибуты класса: счётчик total_items для подсчёта общего количества товаров в корзине и cart_size для ограничения размера корзины (максимального количества товаров в ней).
Для изменения значения cart_size удобно написать метод класса set_cart_size(), так как он позволит не просто изменить значение атрибута, а прописать дополнительную логику: максимальный размер корзины должен быть больше или равен текущему количеству товаров в ней:
class Item:
total_items =0 # Начальное количестве товаров корзине cart_size = 100 # Максимальное количество товаров в корзине
def__init__(self, name: str, price: int):
# Проверяем, не превышен ли размер корзины
if Item.total_items >= Item.cart_size:
print("Достиг нут лимит товаров в корзине ) return
self.name = name # Название товара
self.price = price # Цена товара
Объектно-ориентированное программирование 374
Item.total items += 1
(fficlassmethod def set_cart_size(cls, newcartsize: int) -> None: # Проверяем, что товаров в корзине меньше, чем новый размер корзины if new_cart_size >= cis.total_items: cis.cart_size = new_cart_size print(f"Размер корзины изменён на {cis.cart_size} ) else:
print(f"Размер корзины должен быть больше {cis.total_items]')
Теперь мы можем изменить размер корзины через метод класса, который осуществляет дополнительную проверку:
iteml = Item("beep", 10ч)
item2 = Item( вентиляторг, 1250)
print(f Всего товаров в корзине: {Item.total_items}")
Item.set_cart_size(l)
#	Вывод: Размер корзины должен быть больше 2
Itern.set_ca rt_s ize(20)
#	Вывод: Размер корзины изменён на 20
При использовании атрибутов класса в методе класса используется именно ссылка на класс cis, так как такой метод не имеет доступа к его экземплярам. Это делает код более гибким для наследования
Если другой класс, например Digitalltem для электронных товаров, наследуется от Item, то атрибуты cis. total_items и cls.cart_size будут ссылаться на атрибуты дочернего, а не родительского класса. Это позволит каждому дочернему классу вести свой собственный, независимый счетчик, если это необходимо.
Статические методы
Помимо методов класса и его экземпляра, в Python представлен ещё один тип методов, называемых статическими методами. Они отличаются от двух предыдущих тем. что не имеют доступа ни к экземпляру класса (self), ни к самому классу (cis).
Статические методы объявляются с помощью декоратора (Sstaticmethod и представляют собой обычные функции, которые помещены внутрь класса для организации кода. Они не зависят от состояния объекта или атрибутов класса, а их основным назначением является логическая группировка связанных функций.
Например, создадим класс Math для математических вычислений. В нём Объектно-ориентированное программирование 375
может быть функция factorial() для вычисления факториала числа и функция add() для сложения двух чисел. Эти функции не используют ни атрибуты класса, ни атрибуты экземпляра, но логически принадлежат к одной труппе, поэтому мы помешаем их в этот класс как статические методы:
class Math:
(ffistaticmethod def factorial(n): if n == a or n == 1: return 1
return n * Math.factorial^ - 1)
(Sstaticmethod def add(x, y): return x + у
Такие методы вызываются напрямую через имя класса как в самом классе, так и вне его:
print(f"5' = {Math.factorial(5)}' ) # Вывод: 5! = 120
print(f"5 + 3 = {Math.add(5, 3)}' ) # Вывод: 5 + 3 = 8
Статические методы полезны, когда функция логически принадлежит классу, но не зависит от его состояния, например, является вспомогательной функцией.
Примеры
Пример 1. Продажа билетов в кинотеатре
Класс Ticketsale описывает продажу билетов кинотеатре. Базовая цена билета base_price и скидка discount являются общими для всех экземпляров класса, поэтому они объявлены как атрибуты класса. Метод класса apply discount () изменяет скидку, которая используется при расчёте итоговой стоимости билета в методе экземпляра класса calculate_final_price():
class Ticketsale:
...Описывает продажу билете,в, учитывая цену, скидки и ограничения..
# Атрибуты класса: общие настройки
base_price = 100 # Цена билета по умолчанию
discount = 0 # Скидка по умолчанию
def___init__(self, movie_title: str):
"""Конструктор класса TickerSale.
Объектно-ориентированное программирование 376
Параметры:
movie_title: Название фильма.
М *1 II
self .movie_title = rrovie_title
(Sclassmethod
def apply_discounr(cls, discount: float) -> None:
"'"‘Метод класса. Изменяет значение скидки, если она больше нуля.
Параметры:
discount: Новая скидка в процентах. П II II
if discount > 0:
cis.discount = discount
print(f Добавлена скидка 1 discount} %") else:
print("Скидка должна быть больше нуля )
def calculate_fina]_price(self) -> float:
"""Рассчитывает и возвращает итоговую цену.""" return self.base_price * (10u - self.discount) / 100
#	Используем метода класса для изменения общей скидки
Ticketsale.apply_discount(5) & Добавляем скидку 5%
#	Создадим билет
ticket = Ticketsale("Дракула (2025) )
final_price = ticket.calculate_final_price()
print(f"итоговая цена билета на фильм {ticket.movie_title}; {final_price} РУб.’)
Вы вид:
Добавлена скидка 5 %
Итоговая цена билета на фильм Дракула (2025): 95.о руб.
Пример 2. Работа с опенками студентов
Класс Studentscore предоставляет функции для работы с оценками студентов. Статический метод calculate_average() рассчитывает средний балл, а метод core_to_grade() преобразует баллы в оценку. Эти методы можно использовать без создания экземпляра класса Studentscore:
class Studentscore:
"""Предоставляет функции для работы с оценками студентов."""
(fflstaticmethod def calculate_average(scores: list[int]) -> float:
Объектно-ориентированное программирование 377
..Статический метод. Рассчитывает среднее арифметическое.
Параметры:
scores: Список с баллами или оценками студента.
Возвращает:
Среднее арифметическое.
II II II
if not scores: return 0.0
return sum(scores) I len(scores)
(Sstaticmethod def srore_to_grade(scnre: int) -> int: """Статический метод. Преобразует баллы в оценку.
Параметры:
score: Набранные баллы.
Возвращает-
Оценка по пятибалльной шкале.
It II II
if score >= 85: return 5
if score >= 75: return 4 if score >= 60: return 3 return 2
#	Перевод баллов в оценку score = 85
grade = Studentscore, sccretugi'ade(score) print(f"Baflfl {score)- соответствует оценке (grade}")
#	Расчет среднего арифметического student_grades = [5, 4, 4, 3, 5] avg = StudentScore.calculate_average(student_grades) print(f Среднее арифметическое оценок <studentgrades}: {avgj’)
Вывод:
Балл 85 соответствует оценке 5
Среднее арифметическое оценок [5, 4, 4, 3, 5]: 4.2
Пример 3. Разработка шры
Класс GameSession описывает запуск игры с настраиваемой сложностью и количеством жизней. Допустимые уровни сложности хранятся в атрибуте класса difficulty_levels, а количество жизней по умолчанию в атрибуте класса de-fault_lives.
Объектно-ориентированное программирование 378
Метод класса create_default_game() создаёт игру с настройками по умолчанию, в котором уровнем сложности является первый элемент списка diffi-culty_levels.
Метод экземпляра класса start() запускает игру с переданными настройками. При этом статический метод _validate_level() является вспомогательной функцией для создания экземпляра класса GameSession и проверяет, что переданный конструктору класса уровень совпадает с одним из значений в списке dif-f iculty_levels.
class Gamesession:
"""Описывает запуск игры с настройками сложности.....
# Атрибуты класса: константы сложность и количества жизней difficuity_levels = ["Легко'., "Тяжело , "Экстримально"] default_lives = 3
def____init__(self, difficulty: str, lives: int=default_lives):
"""Конструктор класса GameSession.
Параметры:
difficulty: Сложность.
lives: Количество жизней.
# Проверка через статический метод
if not self._validate_level(difficuity):
raise ValueError(f"Неизвестный уровень сложности")
self.difficulty = difficulty self.lives = lives
gstaticmethod
def _validate_level(level: str) -> bool:
...Статический метод. Проверяет, что уровень сложности допустим.
возвращает:
True, если сложность является допустимой, иначе - False.
It 11 м
return level in GameSession.difficulty_levels
(alclassmethod
def create_default_game(cls) -> None:
...Метод класса. Запускает игру с настройками по умолчанию......
print(f 'Запуск игры с настройками по умолчанию. )
print(f 'Сложность: {cls.difficulty_levels[0]}. Жизни: {cls.de-
fauit_lives}")
def start(self) -> None:
"""Запускает игру с переданными настройками.
Объектно-ориентированное программирование 379
print(f"Запуск игры с пользовательскими настройками... '')
print(f"Сложность: {self.difficulty} Жизни: {self.lives} )
#	Создание игры по умолчанию GameSession.create_default_game()
#	Создание игры с собственными настройками hard_game = GameSession( Тяжело", 2) hard_game.start()
Вывод:
Запуск игры с настройками по умолчанию...
Сложность: Легко, жизни: 3
Запуск игры с пользовательскими настройками...
Сложность: Тяжело, жизни: 2
Итоги
S Атрибуты класса - это атрибуты, которые принадлежат самому классу и являются общими для всех его экземпляров. Они определяются внутри тела класса, но вне любого метода.
S Методы класса - это методы, которые относятся к классу в целом, а не к его экземплярам.
S Методы класса создаются с помощью декоратора @classmethod. Первым аргументом они принимают ссылку не на объект, а на сам класс, который принято обозначать cis.
J С татические методы - это методы, которые не имеют доступа ни к экземпляру класса, ни к самому классу. Они представляют собой обычные функции. помещённые внутри класса для организации кода.
J Статические методы создаются с помощью декоратора @staticmethod
Задания для самопроверки
1.	Можете ли вы изменить атрибут класса через экземпляр этого класса, а не через сам класс?
2.	Почему статические методы не принимают ни ссылку на экземпляр класса self, ни ссылку на сам класс cis?
3.	Создайте класс для планеты Planet с атрибутом класса planet_count = 0 для счётчика созданных планет. В конструкторе_init_(), при каждом созда-
нии нового объекта, увеличивайте значение этого атрибута на 1. Создайте
Объектно-ориентированное программирование 380
минимум 3 экземпляра класса Planet и выведите на экран итоговое значение атрибута planet_count.
4.	Создайте класс для команды Team с атрибутом класса sport = "Футбол". Создайте метод класса change_sport(cls, new_sport), который изменяет значение атрибута spurt. Измените вид спорта на "Хоккей", вызвав Team.change_sport("Хоккей"), и выведите на экран значение атрибута sport.
5.	Создайте класс для вспомогательных функций Utility. Создайте статический метод is_valid_email(email_address), который принимает строку email_address и возвращает True, если в ней есть символы @ и . (точка), иначе -False Вызовите этот метод для строк "legolas777@example.ru" и "big_thranduil".
5.6 ПЕРЕГРУЗКА АРИФМЕТИЧЕСКИХ ОПЕРАТОРОВ И ОПЕРАТОРОВ СРАВНЕНИЯ
Встроенные типы данных поддерживают множество стандартных операций. Например, числа поддерживают все арифметические операции, а строки мы можем складывать и умножать на число.
Однако, когда вы создаёте собственный класс, Python использует поведение по умолчанию и не знает, какое логическое значение вы вкладываете в операции сложения или сравнения для объектов этого класса.
Например, давайте вспомним, что в математике есть такое понятие как вектор - направленный отрезок, соединяющий две точки. Тогда координатами вектора ЛВ с началом в точке А с координатами у^) и концом в точке В с координатами (х2; у2) является разность соответствующих координат конца и начала, то есть (х2 — xlfy2 — уг). Поэтому при создании класса вектора Vector мы можем инициализировать его координатами х и у, представляющими собой ею смещение относительно начала координат : class Vector:
def _init_(self, x: int | float, y: int | float):
self.x = x self.у = у
def _repr_(self) -> str:
return f"Vector(x={self.x}, y={self.y})"
Теперь давайте создадим два вектора и попробуем их сложить:
Объектно-ориентированное программирование 381
vectorl = Vector(3, 5)
vector'2 = Vector(l, b) print(vectorl + vector?)
#	Ошибка: ТуреЕггог: unsupported operand type(s) for +: 'vector' and 'Vector'
Ожидаемо, но мы столкнулись с ошибкой ТуреЕггог, ведь Python не умеет складывать объекты класса Vector. Однако Python предоставляет специальный механизм, называемый nepei рузкой операторов, который позволяет определять специальное поведение операторов (таких как +, - или * и другие) для пользовательских классов. Это осуществляется с помощью специальных магических методов, например, для операции сложения используется метод______add__().
Flepei рузка оператора сложения
Для сложения двух векторов следует сложить соответствующие координаты этих векторов. То есть суммой вектора с координатами (3; 5) и вектора с координатами (1; 6) должен быть новый вектор с координатами (4; 11).
В классе Vector мы можем реализовать это с помощью метода_______add___(),
который принимает ссылку на экземпляр класса self и объект other, с которым производиться сложение. В данном случае объект other также является экземпляром класса Vector, поэтому мы можем получить координаты этою вектора как атрибуты этого объекта:
class Vector:
def __init_(self, x: int | float, y: int | float):
self.x = x self.у = у
def __repr_(self) -> str:
return f"Vector(x={self.x}, y={self.y})"
def __add__(self, other: "Vector ) -> ’Vector":
new_x = self.x + other.x new_y = self.у + other.у return Vector(new_x, new_y)
При этом арифметические операции не должны изменять исходные объекты, поэтому внутри метода _____add__() мы определяем, как сложить компо-
ненты. и возвращаем новый объект класса Vector.
Теперь, когда Python встретит оператор сложения (+) между двумя объектами класса Vector, он будет искать метод___add___() в левом операнде:
vectorl = Vector(3, 5)
vector? = Vector(l, 6)
Объектно-ориентированное программирование 382
print(vectorl + vecror2)
#	Вывод: Vector(x=4j y=ll)
Благодаря перегрузке, мы можем использовать знакомый и интуитивно понятный оператор + для выполнения специфической для нашего класса операции сложения векторов.
Ilepei рузка арифметических операторов
Перегрузка арифметических операторов позволяет объектам собственных классов участвовать во всех стандартных арифметических операциях, доступных для встроенных типов данных. То есть мы можем определить не только сложение, но и умножение, нахождение остатка от деления и даже возведение в степень.
При этом магические методы, используемые для перегрузки бинарных операторов, принимают два аргумента:
о self - ссылка на объект, на котором вызывается метод (левый операнд); о other - ссылка на объект, используемый в операции (правый операнд).
Когда вы пишете выражение с оператором, Python автоматически переводит его в вызов метода, где порядок аргументов строго определен. Поэтому рассмотренный ранее метод_add__() в выражении vectorl + vector? преобразуется в vectorl._____________add_(vector?), то есть он вызывается именно на левом опе-
ранде vectorl.
Таблица 18 - Методы перегрузки арифметических операторов
Метод	Оператор	Операция	Сокращение 01
	add	(self, other)	+	Сложение	Addition
	sub	(self, other)	-	Вычитание	Subtraction
	mul_(self, other)	*	Умножение	Multiplication
	truediv	(self, other)	/	Деление	True division
	floordiv	(self, other)	//	Целочисленное деле-	Floor division
		ние	
	mod	(self, other)	0/ /0	Остаток от деления	Modulo
	pow	(self, other)	**	Возведение в степень	Power
Объекто-ориентированное программирование 383
Для примера давайте определим операцию умножения вектора на число. Для этого нужно умножить каждую координату вектора на это число:
class Vector:
def __init_(self, x: int I float, y: int | float):
self.x = x self.у = у
def __repr_(self) -> str:
return f"Vector(x={self.x}, y={self.y})"
def __mul__(self, scalar: int | float) -> "Vector":
new_x = self.x * scalar new_y = self.у * scalar return Vector(new_x, new_y)
Обратите внимание, что необязательно называть второй аргумент именем other. Например, здесь более наглядным будет назвать число, на которое умножается вектор, именем scalar
Теперь умножим вектор с координатами (2; 7) на число 2:
vector = vector(2, 7) print(vector * 2) # Вывод: Vector(x-4, y-14)
В математике, от перемены мест множителей произведение не меняется, поэтому давайте умножим число 2 на экземпляр класса Vector:
vector = Vector(2, 7)
print(2 * vector)
#	Ошибка: ТуреЕггог: unsupported operand type(s) for *: 'int' ana 'Vector'
И здесь мы столкнёмся с исключением ТуреЕггог, ведь Python вызывает метод___mul____() на левом операнде - целом числе вс 1 роенно! о типа int, который
не работает с объектами класса Vector.
Однако на самом деле в таком случае Python не сразу вызывает исключение. а пробует вызвать отражённый метод на правом операнде. Такие методы отличаются от тех, которые мы уже рассмотрели, только префиксом «г» (от англ. reflected) в начале.
Другими словами, если оператор не определён для левого операнда, то Python сначала ищет отражённый метод с префиксом «г» в правом операнде и только потом вызывает исключение.
Объектно-ориентированное программирование 384
Таблица 19 - Отражённые методы перорузки арифметических операторов
Мет од	Оператор	Операция
	radd	(self, other)	+	Сложение (отражённое)
	rsub	(self, ’-ther)	-	Вычитание (отражённое)
	rmul	(self, other)	*	Умножение (отражённое)
	rtruediv	(self, other)	/	Деление (отражённое)
	rfloordiv	(self, other)	//	Целочисленное деление (отражённое)
	rmod	(self, other)	%	Остаток от деления(отражснное)
	rpow	(self, other)	**	Возведение в степень (отражённое)
И если в классе Vector определить метод_______rmul___(), то мы можем умно-
жать как целые, так и вещественные числа объекты этого класса:
class Vector:
def __init__(self, x: int | float, y: int | float):
self.x = x self.у = у
def __repr__(self) -> str:
return f"Vectoi(x={self.x}, y={self.y})"
def___mul___(self, scalar: int | float) -> "Vector":
new_x = self.x * scalar new_y = self.у * scalar return Vector(new_x, new_y)
def __rmul__(self, scalar: int | float) -> "Vector":
return self.__mul__(scalar)
vector = Vector(2, 7)
print(2 * vector)
#	Вывод: Vector(x=4j y=14) print(l.5 * vector)
#	Вывод: vector(x=3.0, у=10.5)
Здесь отражённый метод____rmul__() просто возвращает метод___mul__().
в котором остаётся вся логика умножения вектора на число. Это равносильно выражению self * scalar.
При этом методы с префиксом «г» не заменяют обычные методы, а вызываются только в том случае, если операция не поддерживается левым операндом. Поэтому для поддержки умножения вектора на число всё равно придётся писать
Объектно-ориентированное программирование 385
обычный метод__mul__().
Также мы уже знаем, что арифметические операторы можно совмещать с оператором присваивания, поэтому Python позволяет определить такие операторы для объектов собственных классов. Для этого достаточно к обычным методам добавить префикс «Ь> (от англ, in-place).
Таблица 20 - Методы перегрузки арифметических операторов с присваиванием
Метод	Оператор	Операция
	iadd	(self, :4her)	+=	Сложение с присваиванием
	isub	(self, other)	- =	Вычитание с присваиванием
	imul	(self, other)	* —	Умножение с присваиванием
	itruediv	(self, other)	/=	Деление с присваиванием
	if loo rd iv	(self, other)	//=	Целочисленное деление с присваиванием
	imod	(self, other)	0/ /о=	Остаток от деления с присваиванием
	ipow	(self, other)	**-	Возведение в степень с присваиванием
Рассмотренные ранее методы, например,_____add___(). создают и возвращают
новый объект, оставляя исходный объект неизменным. Однако методы с префиксом «i» изменяют состояние объекта self и возвращают его же.
Например, определим операцию вычитания с присваиванием: class Vector:
def _init__(self, x: int | float, y: int | float)'
self.x = x self.у = у
def _repr_(self) -> str:
return f"Vector(x={self.xj, y={self.y})"
def__isub__(self, other: "Vector') -> "Vector":
self.x -= other.x self.у -= other.у return self
vectorl = Vector(10, 6) vector2 = Vector(5, 2) vectorl -= vector2 print(vectorl)
#	Вывод: Vector(x=5j y-4)
Объектно-ориентированное программирование 386
Когда Python видит операцию vectorl -= vector2. он вызывает метод vector!.._isub_(vector2), который изменяет исходный объект vectorl. Однако,
если метод__isub()__не был определён. Python попытается выполнить операцию через___________sub()_, а затем присвоить результат обратно переменной.
Першрузка операций сравнения
Перегрузка операторов сравнения дает возможность определить, что означает, что один объект равен другому или нет, а также больше или меньше его.
Например, если вы создаете свой собственный класс и не реализуете метод перегрузки оператора проверки на равенство (==), то этот оператор по умолчанию будет вести себя так же, как оператор is. то есть проверять, ссылаются ли две переменные на один и тот же объект в памяти
Вместо того чтобы полагаться на сравнение адресов объектов в памяти, следует настроить сравнение по тем логически значимым атрибутам, которые действительно определяют сущность и взаимосвязь объектов.
Таблица 21 - Методы перегрузки операторов сравнения
Метод	Оператор	Операция	Сокращение от
	eq	(self, other)	==	Равенство	Equal
	ne	(self, other)	! =	Неравенство	Not equal
_gt	(self, other)	>	Строго больше	Greater than
	It	(self, other)	<	Строго меньше	Less than
	ge	(self, other)	>=	Больше или равно	Greater or equal
	le	(self, other)	<=	Меньше или равно	Less or equal
Представим класс для денег Money, который хранит сумму amount и валюту currency. Два объекта Money логически равны, если их сумма и валюта совпадают:
class Money: def_______init_(self, amount, currency):
self.amount = amount self.currency = currency
def __eq__(self, other: "Money") -> bool:
#	Проверка равенства количества same_amount = self.amount == other.amount
Объектно-ориентированное программирование 387
#	Проверка равенства валюты same_currency = self.currency == other,currency return same_amount and same_currency
Метод проверки на равенство_______eq__() является одним из самых важных.
По умолчанию он наследуется от object и просто проверяет, являются ли два объекта одним и тем же объектом в памяти. Переопределяя его, вы определяете логическое равенство по собственным требованиям:
moneyl = Мопеу(100, "RUB )
money2 = Мопеу(100, "rub1)
топеуЗ = Money(150, "USD )
rr.oney4 = Money(100, "EUR")
print(money] == money2)
#	Вывод: True (одинаковая сумма и валюта)
print(moneyl == топеуЗ)
#	Вывод: FaLse (разная сумма)
print(moneyl == money4)
#	Вывод: FaLse (разная валюта)
Если вы определяете____________eq___, то вам необязательно определять метод проверки на неравенство_______________пе_(), так как Python использует следующую логику:
гтюпеу! != money2 эквивалентно not (moneyl == money2):
print(moneyl != money2)
#	Вывод: FaLse (одинаковая сумма и валюта)
print(moneyl 1= топеуЗ)
#	Вывод: True (разная сумма)
print(moneyl != топеу4)
#	Вывод: True (разная валюта)
Также в классе Money мы можем определить, что один объект больше другого. если у него больше сумма, при условии, что валюта одинакова:
class Money: def ______init__(self, amount: int | float, currency: str):
self.amount = amount self.currency = currency
def __It__(self, other: "Money") -> bool:
if self.currency != other.currency:
raise ValueError('Нельзя сравнивать деньги в разных валютах") return self.amount < other.amount # Сравнение no сумме
moneyl = Мопеу(1й0, "RUB") money2 = Money(20O, "RUB") print(moneyl < money2) # Вывод: True (100 < 200)
Объекто-ориентированное программирование 388
Аналогичным образом определяются и другие операторы сравнения.
Взаимодействие с объектами разных типов данных
Бинарные операторы выполняют операцию над двумя операндами. И если левый операнд self гарантированно является ссылкой на экземпляр разрабатываемого класса, то в качестве правого операнда other может быть передан объект любого типа.
Однако функция isinstance() позволяет проверить тип правого операнда other перед совершением операции внутри магического метода. Если она возвращает True, то операция совершается, но если объект other имеет неподходящий тип, и функция возвращает False, то возможно два варианта-
о Возврат константы Not Implemented, если есть вероятность, что правый операнд может совершить операцию с помощью отражённого метода. То есть операция является коммутативной и её результат не зависит от порядка операндов
о Вызов исключения с помощью ключевого слова raise, если операция не должна быть разрешена, даже если объект other имеет отраженный метод.
Представим класс Score, который хранит количество очков value. Объекты этого класса должны складываться друг с другом, а также с обычными числами: class Score:
def__init__(self, value: int | float):
self.value = value
def _repr__(self) -> str:
return f"Score({self.value})"
def _add___(self, other: "Score") -> "Score":
#	Если other - это объект класса Score if isinstance(other, Score):
new_value = self.value + other.value return Score(new_value)
#	Если other - это число elif isinstance(other, (int, float)): newvalue = self.value + other return Score(new_value)
return Notlmplemented
def _radd__(self, other: "Score ) -> "Score":
return self._add__(other)
Объектно-ориентированное программирование 389
Проверка типа объекта other позволяет реализовать как сложение двух объектов класса Score:
scorel = Score(1И)
score2 = Score(5) print(scorel + score2) # Вывод: Score(lS)
Так и сложение объекта этого класса с числом:
print(score2 + 25) # Вывод: Score(30)
При этом отражённый метод_______radd___() возвращает____add__(), так как
сложение, как и умножение, коммутативно, то есть порядок слагаемых не важен: print(10 + scorel) # Вывод: Score(20)
Если объект other не является ни объектом класса Score, ни числом, то возвращается константа Notlmplemented. указывающая Python не сразу вызывать исключение, а вызвать отражённый метод на нравом операнде. Поэтому сложение объекта класса Score со строкой приведёт к исключению ТуреЕггог-print(scorel + "12") # Вывод: ТуреЕггог: unsupported operand type(s) for +: 'Score' and 'str'
Однако это исключение возникает после того, как была сделана попытка вызвать отражённый метод_____radd___() на объекте класса str
Если такое поведение недопустимо и исключение следует вызывать сразу в магическом методе, то используется ключевое слово raise. После него указывается тип вызываемого исключения, а в скобках может быть указан текст, который будет выводиться на экран.
Например, запретим операцию сложения для любого объекта, не являющегося экземпляром класса Scoreclass Score: def ___________init_(self, value: int | float);
self.value = value
def __repr_(self) -> str:
return f"Score({self.value})'
def___add__(self, other: "Score") -> "Score":
if isinstance(other, Score): new_value = self.value + other.value return Score(new_value)
raise ТуреЕггог(Г'Нельзя сложить Score с типом {type(other)} )
Объектно-ориентированное программирование 390
def __redd__(self, other: "Score' ) -> "Score":
return self.__add__(other)
Теперь исключение вызывается сразу же на объекте класса Score, а также выводится именно то сообщение об исключении, которое мы написали:
scorel = Score(10)
p^int(scorel + 2)
#	иывод: ТуреЕггог: Нельзя сложить Score с типом scLass 'int'>
Примеры
Пример 1. Управление временем
Класс Duration описывает отрезок времени в минутах (например, длительность фильма или перерыва) с помощью атрибута minutes, и обеспечивает вычитание одного времени из другого, выполнение целочисленного деления (сколько полных раз умещается одно время в другом) и проверку на равенство:
class Duration:
...Описывает отрезок времени в минутах."""
def _init___(self, minutes: int):
...Конструктор класса Duration.
Параметры:
minutes: Количество минут в отрезке времени, if и и
self.mi nates = minutes
def __str__(self) -> str:
"""Пользовательское (-писание объекта.""" return f'{self.minutes} мин."
def___sub__(self, other: "duration") -> 'Duration":
"""Перегрузка оператора вычитания (-)....
if not isinstance(otlrer, Duration):
return Notlmplemented
new_minutes = self.minutes - other.minutes return D-aration(new_minutes)
def___floo^div__(self, ,ther: “Duration") -> int:
"""Перегрузка оператора целочисленного деления (//)."""
if not isinstance(other, Duration) or other.minutes == 0:
return Notlmplemented
return self.minutes // other.minutes
Объектно-ориентированное программирование 391
def __eq__(self, other: object) -> bool:
"""Перегрузка оператора равенства (==).""" if isinstance(other, Duration):
return self.minutes == other.minutes
return Notlmplemented
film = Duration(120) # Длительность фильма trailer = Duration(S) # Длительность трейлера break_time = Duration(15) # Длительность перерыва
#	Вычитание (-)
remaining = film - break_time
print(f {film} - {trailer} - {break_time} = {film - trailer - break_time}')
#	Целочисленное деление (//)
print(f"{film} // {trailer} = {film // trailer} мин.")
#	Равенство (-=)
duration! = Duration(60)
duration2 = Duration(60)
print(f"{film} == {trailer}? {film == trailer} )
print(f"{fil.m} != {break_time}? {film != break_time} )
Вывод:
120 мин. - 5 мин. - 15 мин. = 100 мин.
120 мин. // 5 мин. = 24
120 мин. == 5 мин.? False
120 мин. != 15 мин.? True
Пример 2. Управление временем доставки
Класс DeliveryTirne описывает время доставки в часах с помощью атрибута hours, и поддерживает добавление задержки и проверку па соблюдение временного лимита с помощью операций сложения с присваиванием (+=) и сравнения на меньше или равно (<=). При этом второй операнд может быть как экземпляром этого класса, так и целым или вещественным числом:
class DeliveryTirne:
..Описывает время доставки в часах...
def___init__(self, hours: float):
"""Конструктор класса DeliveryTirne.
Параметры: hours: Время в часах.
М «I II self.hours = hours
Объектно-ориентированное программирование 392
def __str__(self):
"""Пользовательское описание объекта...
return f {self.hours} ч."
def___iadd__(self, other: 'Delivery!ime") -> "DeliveryTime":
"""Перегрузка оператора сложения с присваиванием (+=).""” if isinstance(other, DeliveryTime):
self.hours += other.hours
elif isinstance(other, (int, float)):
self.hours += other # Разрешаем добавление числа (часов)
return Notlmplemented
def __le_(self, other: "DeliveryTime ) -> cool:
"""Перегрузка эператора меньше или равно (<=).""" if isinstance(other, DeliveryTime):
return self.hours <= other.hours
if isinstance(other, (int, float)):
return self.hours <= other # Сравнение с числом (лимитом в часах)
return Notlmplemented
standard_time = TeliveryTime(24.0) # Стандартное время доставки delay = iieliveryTime(3.5) # Задержка
1imit = 48 # Лимит как целое число
#	Сложение с присваиванием (+=)
delivery = standard_time
delivery += delay
print(f‘ .:ремя доставки с умелом задержки, {delivery}")
delivery += 1.0 # Добавляем еще час (целое число)
print(f"Время доставки после еще часа: (delivery)")
#	Сравнение (<-)
print(f ;ремя доставки <= {limit} часов? {delivery <= limit}") print(f"BpeMfl доставки <= 25 часов? {delivery <= 25}')
Вывод:
Время доставки с учетом задержки: 27.5 ч.
Время доставки после еще часа: 28.5 ч.
Время доставки <= 48 часов? True
Время доставки <= 25 часов? False
Пример 3. Стоимость актива на бирже
Класс Price описывает стоимость акции или любого другого актива на бирже, которая имеет целую (рубли) и дробную (копейки) части. Так как
Объектно-ориентированное программирование 393
использование стандартных вещественных чисел float может привести к ошибкам округления, то рубли и копейки хранятся отдельно в соответствующих атрибутах rubles и kopecks. Однако для расчётов и сравнения вся стоимость переводится в копейки _total_kopecks. а рубли и копейки вычисляются в методе _init_():
class Price:
..Описывает стоимость актива."""
def __init_(self, rubles: int = 0, kopecks: int = 0):
"""Конструктор класса Price.
Нормализует копейки, чтобы их было не больше 99.
Параметры:
rubles: Часть стоимости актива в рублях.
kopecks: Часть стоимости актива в копейках.
If fl *1
# Вся стоимость б копейках self,_total_kopecks = rubles * 100 + kopecks
self.rubles  self._total_kopecks // 100 # Целая часть - это рубли self.kopecks = self._total_kopecks % 100 # Остаток - это копейки
def ___str__(self):
..Пользовательское описание обьекта.""" return f"{self.rubles} руб. {self.kopecks} коп."
def ___add_(self, other: "Price") -> "Price":
..Перегрузка оператора сложения (+)..... if not isinstance(other, Price): return Notlmplemented
total_kopecks = self._total_kopecks + other._total_kopecks # Возвращаем новый овъек/т^ используя общее количество копеек return Price(kopecks=total_kopecks)
def _sub_($elf, other: "Price") -> Price": """Перегрузка оператора i-ычитания (-).""“ if not isinstance(other, Price): return Notlmplemented
totalkopecks = self.totalkopecks - other.totalkopecks # Стоимость не может быть отрицательной if totaljkopecks < 0: total_kopecks = 0
return Price(kopecks=total_k4.pecks)
def ___gt_(self, other: "Price') -> bool:
"""Перегрузка строго больше (>).
Объектно-ориентированное программирование 394
Сравнивает по общему количеству копеек. И II II
if not isinstance(other, Price): return Notlmplemented
return self._total_kopecks > other._total_kopecks
def ___eq__(self, other: object) -> bool:
"""Перегрузка оператора равенства (==). Сравнивает количеств.' рублей и копеек. II М II
if not isinstance(other, Price): return Notlmplemented
return self.rubles == other.rubles and self.kopecks == other.kopecks
pricel = Price(rubles=52, kopecks=40) price2 = Price(rubles=178, kopecks=87) price3 = Price(rubles=1024, kopecks=20)
#	Сложение (+)
print(f {pricel} + {price2} = {pricel + price2} )
print(f {price2} + {price3} = {price2 + price3} , ena="\n\n ) # Проверка: 5.50 + 11.50 = 17.00
#	Вычитание (-)
print(f {price2} - {pricel} = {price2 - pricel} )
print(f {price3} - {pricel} = [price3 - pricel}', end="\n\n )
#	Проверка на равенство (==) price4 = price2 print(f'{pricel} == {price2}? print(f {price4} == {price2}>
[pricel == price2} )
[price4 == price2}', end="\n\n )
# Сравнение (>)
print(f {pricel} > {price2}? {pricel > price2} ) print(f {price2} > {price3}? {price2 > price3} )
Вывод:
52 руб. 40 коп. + 178 руб. 87 коп. = 231 руб. 27 коп.
178 руб. 87 коп. + 1024 руб. 20 коп. = 1203 руб. 7 коп.
178 руб. 87 коп. - 52 руб. 40 коп. = 126 руб. 47 коп.
1024 руб. 20 коп. - 52 руб. 40 коп. = 971 руб. 80 коп.
52 руб. 40 коп. == 178 руб. 87 коп.? False
178 руб. 87 коп. == 178 руб. 87 коп.? True
52 руб. 40 коп. > 178 руб. 87 коп.? False
178 руб. 87 коп. > 1024 руб. 20 коп.? False
Объектно-ориентированное программирование 395
Ито1 и
s Flepei рузка опера торов - это механизм, который позволяет определить поведение стандартных операторов (например. +, - или *) для пользовательских классов.
J Магические методы перегрузки операторов переопределяют арифметические операторы, включая операторы с присваиванием, а также операторы сравнения
S Если оператор не определён для левого операнда, то Python сначала ищет оз ражённый метод с префиксом «г» в правом операнде и только потом вызывает исключение.
J Если тип левого операнда проверяется перед совершением операции, то если он не поддерживает эту операцию, то можно явно вернуть константу Notlmplemented для вызова отражённого метода или вызвать исключение с помощью ключевого слова raise.
Задания для самопроверки
1.	Когда Python вызывает отражённые методы перегрузки?
2.	Создайте класс для времени Time, который инициализируется одним атрибутом total_seconds для общего количества секунд. Перегрузите оператор * (умножение) так, чтобы он умножал total_seconds на число, переданное в качестве правого операнда, и возвращал новый объект Time. Для удобного вывода информации об объекте определите метод_str__(). Выведите на экран резуль-
тат операции Time (10) * 3.
3.	Для класса Time из предыдущего задания реализуйте метод отражённого умножения___rmul__() для умножения числа на объект класса Тime. Выведите на
экран результат операции 4 * Time (15).
4.	Создайте класс для товаров Item, который инициализируется атрибутами name для имени и price для цены. Перегрузите оператор == (равенство). Два объекта Item считаются равными, если их атрибуты name и price совпадают. Выведите на экран результат сравнения Item ("Атлас", 320) с Item(“Атлас", 320).
5.	Д1я класса Item из предыдущего задания перегрузите оператор < (строго меньше). Один товар считается меньше другого, если его цена price строго меньше. Выведите на экран результат сравнения item ("Блокнот", 500 j с Кет("Текстовыделитель", 60).
Объектно-ориентированное программирование 396
5.7 ПЕРЕГРУЗКА ОПЕРАТОРОВ КОНТЕЙНЕРА
Возможности перегрузки операторов в Python не ограничиваются базовыми арифметическими операциями и сравнением объектов, так как существуют методы, позволяющие создать свою собственную коллекцию, то есть класс, представляющий собой контейнер элементов, как список или словарь. Объекты такого класса могут быть перебраны в цикле for. использовать оператор in и функцию 1еп(). а также синтаксис обращения по индексу или ключу через квадратные скобки.
Создание итерируемых объектов
Методы____iter__() и __next__() лежат в основе механизма итерации в
Python и позволяют перебирать объекты в цикле for или использовать их в других функциях, которые принимают итерируемый объект, например, sum() или zip(). Также эти методы можно явно вызывать с помощью функций iter() и next(), как мы уже делали для встроенных типов данных, когда рассматривали работу итерируемых объектов.
Метод____iter__() превращает объект в итератор. В простейшем случае,
сам итерируемый объект часто выступает в роли своего итератора, поэтому __iter__() просто возвращает self.
Метод____next__() является основным рабочим механизмом итератора. Он
возвращает следующий элемент в последовательное ги. При этом, когда элементов больше нет, метод обязан вызвать исключение Stopiteration. Именно это исключение сигнализирует циклу for о том, что итерация закончена.
Рассмотрим класс Countdown, объекты которого выполняют обратный отсчет от заданного числа start до нуля:
class Countdown:
def__init__(self} start: int):
self.current = start А Начальное значение
#	Создание итератора из объекта def _iter__(self):
return self
#	Возврат следующего элемента итератора def _next__(self):
if self.current <= 0:
# Условие достигнуто
raise Stopiteration
Объектно-ориентированное программирование 397
#	Возвращаем текущее значение result = self.current
#	Уменьшаем его для следующего шага self.current -= 1
return result
Объект этого класса можно перебрать в цикле for:
countdown = Countdown(S) for number in countdown: print(number, end= )
# Вывод: 5 4 3 2 1
Когда Python выполняет цикл for number in countdown происходит следующая последовательность шагов:
1	Вызывается функция iter (countdown), которая вызывает метод_____iter__()
объекта countdown.
2.	Метод____iter__() возвращает обьект-итератор, В данном случае это сам
объект countdown.
3.	На каждой итерации цикла вызывается функция next() для объекта-итератора. что приводит к вызову метода______next__().
4.	Когда в методе___next__() выполняется выражение raise Stopiteration,
цикл for завершается
Класс Countdown реализует оба метода___iter__() и___next__(). что делает
его одновременно итерируемым объектом и одноразовым итератором И так как состояние self .current изменяется в процессе перебора, то при повторном обращении метод_____next__() сразу вызывает Stopiteration и мы не можем ис-
пользовать этот итератор ещё раз, например, в функции sum(): print(sum(countdown)) # Вывод: в
Решение этой проблемы заключается в разделении этих ролей на два класса. Основной класс должен быть только итерируемым объектом, который создаёт новый независимый обьект-итератор с помощью вспомогательного класса при каждом вызове_____iter__().
В таком случае метод___iter__() класса Countdown должен возвращать ите-
ратор не как объект self, а как экземпляр класса Countdowniterator. Данный класс при инициализации принимает исходное значение start и реализует механизм получения каждого следующего элемента методе_______next__()
class Countdowniterator:
Объектно-ориентированное программирование 398
def___init__(self, start: int):
#	Итератор хранит своё собственное состояние self.current = start
def __next__(self) -> int:
if self.current <= 0:
raise Stopiteration
result = self.current
self.current -= 1
return result
class Countdown: def_______init_(self, start: int):
#	Храним исходное значение, которое никогда не меняется self.start = start
def___iter_(self) -> "Countdowniterator":
#	Каждый вызов iter() создает новый итератор с исходным значением return Countdownlterator(self.start)
Теперь мы можем многократно использовать итератор одного и того же объекта:
countdown = Countdown(3)
#	Первая итерация
for number in countdown: print(number, end=" )
#	Вывод: 321
#	Использование в функции sum() (вторая итерация) total = sum(countdown)
print(f ХпСумма элементов: {total}") #3 + 2+ l = 6
#	Вывод: Сумма элементов: 6
it Третья итерация
for number in countdown: prirtc(number, end=", ) # 3, 2, 1
it Вывод: 3, 2, 1
Персч рузка оператора доступа по ключу/индексу
Методы____getitem___(),____setitem__() и____delitem___() позволяют классу
имитировать поведение упорядоченных коллекций, таких как списки и словари, и обращаться к элементам с помощью квадратных скобок:
о Метод______getitem___(key) предназначен для чтения элемента по ключу key,
Объектно-ориентированное программирование 399
например, числу (индексу).
о Метод______setitem__() предназначен для добавления или изменения зна-
чения value по ключу key.
о Метод______delitem__() предназначен для удаления элемента по ключу key.
При этом метод_____getitem____() возвращает элемент по ключу, а методы
__setitem___() и___delitem___() изменяют сам объект и возвращают None. Также если элемент, к которому обращается один из этих методов не найден, то можно настроить вы зов исключений KeyError или IndexError.
Давайте разработаем класс Cart для корзины в интернет-магазине. У каждой корзины есть размер size, и словарь _items с товарами, где ключами являются названия товаров, а значениями - их количество. Методы_________getitem____().
__set item__() и___delitem___() реализуют работу с этим словарем: class Cart: def _________________________init_(self, size: int=99):
self._items = {} self.size = size
def __getitem__(self, item: str) -> int:
# Вызывается при обращении к объекту по ключу. if item not in self,_items:
# Если товара нет в корзине, возвращаем None print(f"Товар ’{item}' отсутствует в корзине ) return None
#	Возвращаем количество этого товара return self._items[item]
def __setitem__(self, item: str, quantity: int) -> None:
#	Вызывается при установке нового значения по ключу if len(self._items) <= self.size: self._items[item] = quantity print(f"Добавлен/обновлён товар '{item)', {quantity) шт. ) else:
print( Корзина заполнена )
def __delitem__(self, item: str) -> None:
#	Вызывается при удалении объекта по ключу if item in self.-items:
print(f"Товар '{item}- удален из корзины ) del self._items[item] else:
print(f"To^ap {item}' отсутствует в корзине ) return None
Объектно-ориентированное программирование 400
При добавлении нового товара в корзину или при изменении количества уже добавленного товара вызывается метод______setitem__(). который проверяет,
не достигнуто ли максимальное количество товаров в корзине:
cart = Cart()
cart["Простой карандаш"] = 4
#	Вывод- Добавлен/обновлён товар 'Простой карандаш', 4 шт.
cart[ 'Синяя гелевая ручка'] = 2
#	Вывод: Добавлен/обновлён товар 'Синяя гелевая ручка', 2 шт.
cart["Синяя гелевая ручка’] = 3
#	Вывод: Добавлен/обновлён товар 'Синяя гелевая ручка', 3 шт.
При обращении к элементу по ключу вызывается метод get item (), который позволяет быстро узнать текущее количество указанного товара:
print(f"Простые карандаши в корзине: {cart['Простой карандаш']} шт.")
#	Вывод: Простые карандаши в корзине- 4 шт.
print(+"Ластики в корзине: {cart[ Ластик‘]} шт.")
#	Вывод: Ластики в корзине: & шт.
При удалении товара по его названию вызывается метод_______delitem___().
который, если элемент не найден, выводит соответствующее сообщение:
del cart['Синяя гелевая ручка"]
#	Вывод: Товар 'Синяя гелевая ручка1 удален из корзины
del cart["Блокнот AS"]
#	Вывод: Товар 'Ьлокнот А5 ' отсутствует в корзине
Таким образом, тройка методов __________getitem__(), ___setitem___() и
__delitem___() позволяет реализовать полноценную работу с элементами объекта через квадратные скобки. При этом мы можем написать любую логику, как например, обычно при отсутствии ключа в словаре вызывается исключение КеуЕггог, но в нашем классе Cart мы просто выводит информационное сообщение об этом, но не прерываем выполнение программы.
Першрузка оператора in
Оператор принадлежности (in) используется для проверки, находится ли элемент в коллекции, например, в списке, словаре или множестве. Для собст венных классов вы можете переопределить это поведение, определив логику членства внутри вашего объекта.
Это реализуется с помощью магического метода _________contains____(self,
item), который принимает ссылку на объект self и элемент item, принадлежность которого проверяется.
Объектно-ориентированное программирование 401
Рассмотрим класс Inventory для хранения предметов в игре. Инвентарь может вмешать в себя не более, чем size предметов, а названия предметов хранятся в списке stock:
class Inventory:
def__init_(self, stock: list, size: int):
self.size = size self.stock = stock
def _contains__(self, item: str) -> bool:
return item in self.stock
def aad_item(self, item: str) -> None: if len(self.stock) <= self.size: self.stock.append(item) else:
print( Достигнуто максимальное количество предметов )
Метод____contains__() проверяет наличие строки item именно в атрибуте
stock, а не size:
backpack = Inventory([ Лампа', "Веревка"], 25)
bac kpack.add_item("Нож")
print(f"Лампа в инвентаре? {'Лампа' in backpack) )
#	Вывод: Лампа в инвентаре? True
print(f Палатка в инвентаре? {'Палатка' in backpack) )
#	Вывод: Палатка в инвентаре? False
Если вы не переопределите метод___contains___() для своего класса. Python
будет пытаться выполнить операцию item in obj путем итерации по объекту. Для этого сначала ищется метод___iter___(), и если он есть, то создаётся итератор
и элементы перебираются в поисках искомого. Если метод_______iter__() тоже отсутствует, то ищется метод_________________________________________getitem_() для обращения к элементам по ключу.
Но если объект не содержит ни один из этих методов, то Python вызывает исключение ТуреЕггог.
Пере» рузка функции 1еп()
Метод____1еп__() позволяет определить поведение функции 1еп(), если ей
будет передан экземпляр собственного класса. Его необходимо реализовать так. чтобы он возвращал логически обоснованное целое число, представляющее длину или размер экземпляра этого класса. При этом метод_____1еп___(self) при-
нимает только ссылку на экземпляр класса self и должен вернуть неотрицательное целое число.
Объектно-ориентированное программирование 402
Представим класс TaskManager, который управляет списком задач. Длина этого объекта должна соответствовать количеству невыполненных задач. Сам список задач хранится в атрибуте tasks - словаре, новые элементы в который добавляются с помощью метода add_tasks()’
class TaskManager: def _____init__(self):
#	Список задач, где каждая задача - это словарь {Задача: Статус] self.tasks = {}
def add_task(self, description: str., is_done=False) -> None: self.tasks[description] = is_done
def __len__(self) -> int:
#	Считаем задачи, где статус 'is_done' равен FaLse active_counr = 0
For status in self.tasks.values():
if status is False: active_count += 1
return active_count
По умолчанию словарь tasks с задачами- пустой, поэтому функция 1еп() возвращает ноль:
manager = TaskManager()
print(len(manager))
# Вывод: О
Но мы можем добавить в него задачи (выполненные со статусом True и невыполненные со статусом False). Тогда функция 1еп() вернёт количество невыполненных или активных задач:
manager.add_task( 'Написать отчет’ , False)
manager.add_task("Ответить на письме , False)
manager.add_task('Позвонить клиенту", True) # Выполнена
manager.add_task( 'Запланировать встречу", False) print(f"Количество активных задач: {len(manager)} ) # вывод: Количество активных задач: 3
То есть функция 1еп() исключает задачу "Позвонить клиенту", так как её статус равен True.
Перегрузка метода_____len__() имеет дополнительное, очень важное след-
ствие: она влияет на то, как ваш объект интерпретируется с точки зрения его истинности или ложности:
о Объект считается ложным (False), если функция 1еп() возвращает 0.
о Объект считается истинным (True) если функция 1еп() возвращает любое
Объекто-ориентированное программирование 403
число, отличное от 0. то есть объект непустой.
В нашем примере, если нет активных задач или все задачи выполнены и функция 1еп() вернула 0. то объект manager считается ложным, что является логически правильным поведением для пустого менеджера задач
I leper рузка функции bool()
Однако мы можем явно определить истинность или ложность объекта с помощью метода_______bool_(self), который принимает ссылку на объект self и
вызывается всякий раз, когда Python пытается преобразовать экземпляр вашего класса в логическое значение, то есть явно или неявно вызывает функцию bool (). Это происходит, например, в условном выражении, цикле while или при использовании логических операторов.
Метод bool_____() обладает более высоким приоритетом при определении
логического значения объекта, чем метод____1еп _(), и сначала Python пытается
вызвать именно него. Но если ни один из этих методов не определён, то по умолчанию объект считается истинным.
Представим класс Permission для управления правами доступа пользователей. По умолчанию у пользователя нет ролей, то есть множество roles пустое, а статус его активности isactive равен True. Логическое значение объекта должно отражать, действительно ли у пользователя есть какие-либо права, поэтому метод____bool__() возвращает True только в том случае, если у пользова-
теля есть хотя бы одна роль и он активный:
class Permission:
def__init__(self, roles: list | None=None, is_active: bool=True):
self.roles = set(roles) if roles is not None else set() self.is_active = is_active
def _bool__(self) -> bool:
#	Объект считается истинным, если:
#	- у пользователя есть хотя бы одно роло
#	- пользователь активный
return len(self.roles) > 0 and self.is_active
To есть логическая истинность объекта зависит от двух атрибутов roles и is_active, что выходит за рамки простой проверки длины:
admin = Permission(roles=["Администратор , "Модератор'], is_active=True)
if admin: # Вызывается p_admin._booL_()
print(f"Доступ разрешен’)
#	Вывод: Доступ разрешён
Объектно-ориентированное программирование 404
inactive_user = Permission(roles=[ view'], is_active=False)
if not inactive_user:
print(f"Доступ запрещен (Пользователь неактивен)")
#	Вывод: Доступ запрещен (Пользователь неактивен)
Хотя Python может использовать метод_________1еп__() для определения логического значения, явное определение метода__________bool_() предпочтительнее, когда
логика логическою значения сложнее, чем проверка того, является ли объект пустым.
Примеры
Пример 1. Хранение поисковых запросов
Класс SearcnHistory предназначен для хранения пользовательских поисковых запросов в атрибуте queries. История поиска представляет собой список поисковых запросов, добавленных с помощью метода add_query(). Оператор in проверяет наличие запроса в этом списке, функция ] еп () возвращает длину этого списка, а также этот список можно перебрать в цикле for
class SearchHistiH'y:
Описывает историю поиска....
def ___init_(self):
...Конструктор класса SearchHistory...... self.queries = []
def add_query(self, query: str) -> None: .......Добавляет новый запрос в историю.
Параметры:
query: Строка с поисковым запросом.
self.queries.append(query.lower())
def ___contains__(self, item: str) -> bool:
..Перегрузка оператора in.
Проверяет наличие запроса в истории.'..
return item.lower() in self.queries
def ___len__(self) -> int:
"""Перегрузка функции len().
Возвращает количество запросов в истории.""" return len(self.queries)
def____iter_(self) -> "SearchHistory":
..Возвращает итератор и подготавливает копию данных для перебора......
Объектно-ориентированное программирование 405
self._iter_list = self.queries[:]
self._iter_index = 0 return self
def __next___(self) -> str:
"""Возвращает следующий элемент при переборе.""" if self._iter_index >= len(self._iter_list):
raise Stopiteration
result = self._iter_list[self._iter_index] self.iterindex += 1
return result
history = SearchHistoryO
history.add_query( новости спорта)
history.add_query( как пришить пуговицу")
history.add_query( смотреть шерлок онлайн бесплатно’)
#	Наличие поискового запроса в истории
print(f"Был сделан запрос 'новости спорта'? {'новости спорта' in history}'') print(f"Bwn сделан запрос 'милые кружки'? {'милые кружки' in history}')
#	Длина истории
print(f"Количество запросов: {len(history)}')
#	Перебор поисковых запоосов
print С История запросов: )
for query in history: # Вызываются__iter__() и __next__()
prinv(f - {query}')
В этом примере объект SearchHistory сам является итератором. Однако метод___iter____() не просто возвращает объект self, а подготавливает внутрен-
нее состояние для перебора, создавая атрибуты iter_list и iter_index с копией списка queries и начальным индексом, которые и используются в методе __next__(). Это позволяет использовать итератор несколько раз без создания дополнительного класса.
Вывод:
Был сделан запрос 'новости спорта'? True Был сделан запрос 'милые кружки'? False Количество запросов: 3
История запросов:
-	новости спорта
-	купить ворота
-	смотреть шерлок онлайн бесплатно
Объектно-ориентированное программирование 406
Пример 2. Проверка принадлежности точки заданной черри горни
Класс GeographicZone определяет границы территории, используя минимальную и максимальную широту (атрибуты min_lat и max_lat) и долготу (атрибуты min_lon и гпах_1оп). Точка с координатами (кортеж или список из двух чисел), принадлежит этой территории, если её широта (первая координата) находится в диапазоне от min_lat до max_lat, а долгота (вторая координата) - в диапазоне от min ion до max ion. Также объект, описывающий территорию, считается истинным только в том случае, если эта территория имеет ненулевую площадь:
class GeographicZone:
"""Описывает границы территории."""
def __init_(
self, min_lat: int | float, max_lat: int | float, min_lon: int | float, max_lon: int | float ):
"""Конструктор класса GeographicZone.
Параметры:
min_lat: Минимальная широта (от -90.0 до 90.0).
max_lat: Максимальная широта (от -90.0 до 90.0).
min_lon: Минимальная долгота (от -180.И до 180.0). max_.lon: Максимальная долгота (от -180.0 до 180.0). II II II
#	Базовая проверка широты
if not (-90.и <= min_lat <= max_lat <= 90.0): raise ValueError('Неверное значение широты")
#	Базовая проверка долготы
if not (-18И.0 <= min_lon <= 180.0 and -180.и <= max_lon <= 180.0): raise ValueError( Неверное значение долготы")
self.min_lat = min_lat self.max_lat = гпах_1ат self.min_lon = min_lon self.max_lon = max Ion
def __str__(self) -> str:
..Пользовательское описание объекта....
return (f'Широта: от {self.min_iatj до {self.max_lat}." Г'Дилгота: от {self.min_lon} до {self.max_lon}")
def __contains__(self, coordinate: tuplefint | float]) -> bool:
Объектно-ориентированное программирование 407
...Перегрузка оператора in.
Точка находится внутри территории, если её широта находится в пределах минимальной и максимальной широты, и её долгота находится в пределах минимальной и максимальной долготы
lat, Ion = coordinate^], coordinate[1]
is_lat_valid = self.min_lat <= lat <= self.max_lat
is_lon_valid = self.min_lon <= Ion <= self.max_lon
return is_lat_valid and is_lon_valid
def __bool__(self) -> bool:
..Перегрузка функции bool()
Территория считается истинной (True), если тна имеет ненулевую площадь.
return self.min_lat < self.max_lat and self.min_lon < self.max_lon
moscow_zone = GeographicZone(55.5, 56.0, 37.0, 38.0) point_in_moscow = (55.752, 37.618) # Кремль point_in_spb = (59.939, 30.315) # Эрмитаж
# Москва
#	Проверка принадлежности точки территории
print(f Точка {point_in_moscow} в Москве? (point_in_moscow in moscow_zone) ) print(f Точка {point_in_spb} в Москве> {point_in_spb in moscow_zone}")
#	Проверка истинности точки
print(f Москва существует? {bool(moscow_zone)} ")
neverland = GeographicZone(0, 0, 0, 0)
print(f Неверленд существует? {beol(neverland)}")
Вывод:
Точка (55.752, 37.618) в Москве? True
Точка (59.939, 30.315) в Москве> False
Москва существует? True
Неверленд существует? False
Пример 3. Управление нас» ройками в иг ре
Класс GameSettings предназначен для управления настройками в игре. Настройки хранятся в атрибуте data, с которым работаю! операции чтения, записи и удаления элементов по ключу. Метод set_default() изменяет этот словарь и устанавливает значения настроек по умолчанию:
class GameSettings: .......Описывает настройки игры."""
def __init_(self):
Объектно-ориентированное программирование 408
..Конструктор класса GameSettings....... self._data = {} ff Внутреннее хранилище настроек
def set_default(self) -> None: """Устанавлиьает настройки по умолчанию.........
self._data = {
"Яркость": 50,
"Громкость": 75,
"Разрешение": "192<- х 1080",
"Качеств..": "Высокое", }
print(f Установлены настройки по умолчанию:', self._data)
def ___getitem__(self, key: str) -> str I int:
"""Перегрузка чтения значения элемента по ключу."""
#	Если ключа нет, вызывается стандартное исключение КеуЕггог return self._data[key]
def ___seTitem__(self, key: str, value: int | str) -> None:
"""Перегрузка добавления/эбновления значения по ключу."""
#	Можно добавить логику проверки типа или диапазона значений if key == "Яркость" and not (0 <= value <= 100):
print("Яркость должна быть от 0 ди 10и ) return
sel-f. _data [key] = value
def ___de]item__(self, key: str) -> None:
..Перегрузка удаления элемента по ключу........ print(f'Настройка '{key}' удалена ) del self._data[key]
settings = GameSettings() settings.set_default()
#	Добавление нового значения no ключу (__setitem__())
settings["Яркость"] = 80
#	Изменение существующего значения по ключу (__setitem__())
settings["Язык'] = "Русский язык"
ff Чтение значения по ключу (__getitem__())
print(f"TeKyiuee разрешение: {settings['Разрешение*]}")
#	Удаление элемента по ключу (__deLitem__())
del settings['Громкость']
Вывод:
Установлены настройки по умолчанию: {'Яркость': 50, 'Громкость': 75, 'Разрешение': '1920 х 1080', 'Качество': 'Высокое'} ________Текущее разрешение: 1920 х 1080_________________________________________________
Объектно-ориентированное про1раммирование 409
Настройка 'Громкость' удалена
Итоги
Таблица 22 - Методы перегрузки операторов создания контейнера
Метод	Операция	Описание	Возвращаемое значение
	contains	(self, item)	item in obj	Проверка принадлежности	True или False
	len	(self)	len(obj)	Получение размера объекта	Целое неотрицательное число
	iter	(self)	iter(obj)	Создание итератора	Итератор
	next	(self)	next(Gbj)	Последовательное получение элемента объекта	Соответствующий элемент объекта
	bool	(self)	bool(obj)	Определение истинности объекта	True или False
	getitem	(self, key)	objfkey]	Обращение по ключу	Соответствую- щее значение
	setitem	(self, key, value)	objfkey] = value	Запись значения по ключу	None
	delitem	(self, key)	del objfkey]	Удаление значения по ключу	None
Задания для самопроверки
1.	Что делают методы _ iter () и_next_()? Почему они обычно исполь-
зуются вместе?
2.	Создайте класс для студенческой группы StudentsGroup. который инициализируется атрибутом students со списком студентов и groupjiumber для иомера группы. Реализуйте метод_1еп__(), который возвращает количество сту-
дентов в списке students. Создайте произвольный объект класса StudentsGroup и с помощью функции 1еп() выведите на экран его длину.
3.	Создайте класс для недели Week, который инициализируется атрибутом days со значением по умолчанию days = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"]. Реализуйте метод
Объект но-ориентированное программирование 410
__getitem__(). который возвращает день недели по его индексу (например. [0] должен вернуть "Понедельник"). Выведите на экран Week()[l],
4.	Создайте класс для температуры Temperature, который инициализируется атрибутами data для списка температур и scale для шкалы со значением по умолчанию scale = "Цельсий". Реализуйте в этом классе метод___contains__().
который возвращает True, если переданное значение содержится в списке data, иначе - False. Используя оператор in, проверьте содержится ли число 1 в объекте Temperature( [0, -1, 5, 3, -2, -1, 1, 2, 2]) и выведите результат на экран.
5.	Создайте класс для зачётной книжки GradeBook, который инициализируется атрибутом grades для словаря с оценками и со значением по умолчанию grades = {) Реализуйте метод___set item_(), который добавляет в этот словарь
оценку (значение) по имени студента (ключу). Если оценка не является числом, то он должен вызывать исключение ТуреЕггог. Создайте объект класса GradeBook и добавьте в него трёх студентов и их оценки. Выведите на экран значение атрибута grades.
Объектно-ориентированное программирование 411
Глава 6
Модули и пакеты
6.1 ВВЕДЕНИЕ В МОДУЛИ И ПАКЕТЫ
Написание сложной программы с нуля, в котором весь код содержится в одном огромном файле, может быть невероятно долгим и трудозатратным. Гораздо эффективнее не только разбить программу на отдельные файлы (и папки), но и использовать уже готовые части кода - те, что вы ранее написали сами или те, что были разработаны и протестированы другими специалистами.
Файл с расширением .ру называется модулем, а имя этого файла является именем модуля При этом все переменные, функции и классы из одного модуля можно свободно использовать в других модулях. Если код в файле предназначен для непосредственного запуска, а не для повторного использования, то такой файл часто называют скриптом.
Набор связанных модулей, которые совместно обеспечивают определённую функциональность, называется пакетом. Он представляет собой папку, которая содержит несколько файлов с расширением .ру и часто содержит специальный служебный файл_init__. ру. обозначающий, что эта папка является па-
кетом.
Библиотека является более общим понятием, так как в программировании под ней понимают набор готового кода, который можно повторно использовать в своём проекте. Библиотека в Python может быть представлена как одним модулем. так и пакетом, который, в том числе, может содержать другие пакеты.
Импорт модулей
Допустим, в процессе работы над большим проектом, вы создаете множество функций для обработки текста; подсчёт слов, очистка от лишних символов, перевод регистра и так далее. Если эти функции хранятся в вашем основном файле программы, он быстро становится перегруженным и сложным для чтения.
В лаком случае имеет смысл собрать эти функции в один файл, например.
Модули и пакеты 412
text_tools .ру. то есть создать модуль с именем text_tools
Для примера пусть он содержит одну простую функцию count_words(), которая подсчитывает количество слов в строке:
#	text_tooLs.py
def count_words(text: str) -> int: word_list = text.split() return len(word_list)
Теперь, чтобы воспользоваться функционалом этого модуля в другом файле, его следует имноргировшь. Для этого в Python используется специальный оператор import.
Оператор import делает содержимое модуля (функции, классы, переменные) доступным для использования в текущем модуле.
Предположим, главный файл main.py находится в той же папке, что и text_tools.ру.
my_project/ I— main.py ।— text_tools.ру
Обычно именем main.py называют основной запускаемый файл. Он может содержать минимальный код, вызывающий логику из других модулей и пакетов, которые могут быть импортированы разными способами, каждый из которых имеет свои особенности.
Классический импорт
Загрузка всего модуля с помощью оператора import является самым распространённым способом импорта:
import имя_модуля
Эта команда загружает модуль и создает для него пространство имен, то есть систему, сопоставляющую каждому имени соответствующий объект. По умолчанию, пространство имен совпадает с именем самого модуля, и оно позволяет обратиться к любой переменной, классу или функции из модуля прост ранство_имен.имя_объекта
И если мы импортируем весь модуль text_tools. ру, то обращаться к функции count_words() следует через имя этого модуля:
#	main.py
import text_tools
Модули и пакеты 413
text = ’’Примет, это мой новый модуль" word_count = text_tools.count_words(text) print(f"Слов в строке: {wordcount} ) # Вывод: Слов в строке: 5
Такой способ импорта делает код однозначным, то есть вы сразу видите, откуда взялась функция count_words, что помогает избежать конфликтов имен.
Импорт с псевдонимом
Когда имя модуля очень длинное или вы планируете использовать его очень часто, рекомендуется присвоить ему более короткое имя или псевдоним с помощью ключевого слова as:
import имЯ-Модуля as псевдоним
От классического импорта это отличается только тем, что вместо имени модуля вы используете псевдоним, который указали сами:
#	main.py
import text_tools as tt
text = "Псевдонимы делают код короче"
word_count = tt.count_words(text) print(f"CnoB в строке: {wordcount} ) # Вывод- Слов в строке: 4
Выборочный импорт
Если из большого модуля нужна всего одна или две функции, то вместо того, чтобы каждый раз писать его имя или псевдоним, можно использовать ключевое слово from. Оно указывает из какого модуля происходит импорт, а импортируемые объекты перечисляются через запятую после оператора import:
from имя_модуля import объект!, обьект2,...
Тогда к таким объектам можно обращаться так, как будто они написаны в этом же файле:
from text_tools import count_words
text = Голькс эта функция мне нужна"
word_count = count_words(text)
print(f"CnoB в строке: {word_count} )
#	Вывод- Слов в строке: 5
Однако этот способ может привести к конфликту имен. Если у вас уже есть
Модули и пакеты 414
своя функция с именем count_words(), импортированная функция ее перезапишет и код станет двусмысленным.
Импорт всего содержимого
Вместо перечисления импортируемых объектом можно просто указать символ звёздочки (*), которая импортирует всё содержимое модуля:
	from имя _модуля import *
Тогда все переменные, функции и классы модуля будут доступны в текущем пространстве имен, как если бы они были там определены:
from text_toois impart *
text = "Мне нужны все переменные, функции и классы"
word_count = count_words(text)
print(f"CnoB в строке: {word_count}")
#	Вывод: Слов в строке: 7
Однако этот способ считается плохой практикой в Python, так как делает код плохо читаемым и неоднозначным, поскольку мы не знаем, какие именно имена были импортированы, а также высока вероятность конфликтов имён, так как импортированные объекты перезапишут те, что используются в основном файле.
Импорт пакетов
Мы уже рассмотрели импорт модулей, которые находятся в той же папке, что и наш основной файл. Однако проект может содержать не только модули, но
и пакеты, поэтому давайте усложним структуру проекта:
my_project/		#	Главный исполняемый файл проекта
	— main.py		
	— text_analysis/		Пакет для анализа текста
	f— 	init	.py	#	Делает папку text_anaLysis пакетом
	।— text_tools.py	#	Функции для работы с текстом
	— calculations/	#	Пакет для всех математических вычислений
	—-  init .py	#	Делает папку calculations пакетом
	1— basic.py	#	Функции для базовых вычислений
	L— advanced/	#	Подпакет для более сложных вычислений
	I—  init .py	#	Делает папку advanced подпакетом
	।— stats.py	#	Функции для статистического анализа
Здесь модуль text_tools.py выделен в отдельный пакет text_analysis. а также добавлен новый пакет calculations для математических вычислений. В
Модули и пакеты 415
этом пакете находится модуль basic, ру для базовых математических функций и подпакет для более сложных функций advanced, который, в свою очередь, содержит модуль stats.py для функций, связанных со статистикой.
Для работы со сложной структурой папок Python использует иерархию пакетов и два основных метода импорта: абсолютный и относительный.
Абсолютный импорт
Абсолютный импорт заключается в указании полного пути к модулю или пакету, начиная от корневой папки.
В Проводнике полный путь к модулю text_tools.py мог бы выглядеть как C:\tasks\my_project\text__analysis\text_tools.py. При абсолютном импорте важна только часть после названия проекта (папки, в которой находится запускаемый файл), а обратные слэши заменяются на точки:
import text_anaiysis.text_tools
text = "Теперь модуль находится в пакете"
word_count = text_analysis.text_tools.count_words(text)
print(f Слов в строке: {word_count} )
#	Вывод: Слов в строке: 5
Здесь работают те же правила, что и при импорте модуля, поэтому вместо длинного пути удобнее использовать псевдоним:
import text_anaiysis.text_tools as tt
text = "Мне нужен псевдоним :
word_count = tt.count_words(text)
print(f"CnoB в строке: {word_count}')
#	Вывод: Слов в строке: 3
Также можно импортировать не весь пакет, а только одну функцию из модуля text_tools.ру
from text_analysis.text_tools import count_words
text - "Из пакета мне нужна только одна функция"
word_count = count_words(text)
print(f"Слов в строке: {wordcount} )
#	Вывод: Слов в строке: 7
При запуске файла main.py, Python автоматически добавляет путь к папке, в которой находится запускаемый файл, в список sys .path. Эта переменная представляет собой список путей поиска модулей и инициализируется при запуске программы.
Модули и пакеты 416
Когда интерпретатор Python встречает строку import имя_пакета или имямодуля, он в порядке очереди проходит по всем путям в списке sys.path. пока не найдет файл или папку, соответствующую имени. Если модуль или пакет не был найден, то вызывается исключение ModuleNotFoundError Также к этому исключению приведёт нарушение иерархии импорта, если файл с импортом запушен из подпапки, а не из корневой папки проекта.
Относительный импорт
Относительный импорт используется только внутри пакета, чтобы импортировать другие модули, находящиеся рядом. При этом относительный импорт нельзя использовать в файле, который запускается напрямую, а пути указываются относительно текущего модуля.
И если при абсолютном импорте мы идём вниз от корневой папки до нужного модуля, то при относительном импорте, наоборот, поднимаемся наверх с текущего модуля до нужного модуля.
Для относительного импорта используются точки в качестве префикса, а количество точек указывает на сколько уровней мы поднимаемся от текущего модуля и теоретически не ограничено:
о from . имя_модуля import ... - импорт модуля, который находится на одном уровне с текущим файлом.
о from . .имя модуля import ... - импорт модуля, который находится на один уровень выше текущего модуля.
Также если абсолютный импорт использует как конструкции import имя_модуля, так и from имя_модуля import ..., то относительный импорт поддерживает только выборочный импорт from .имя_модуля import ....
Например, ранее мы импортировали функцию mean() из модуля basic.ру в модуль stats.py во вложенном пакете advanced
#	caLcuLations/advanced/stats.ру
from calculations.basic import mean
Относительный импорт позволяет записать это как:
#	caLcut.ations/advanced/stats.ру
from ..basic import mean
Здесь .. означает подняться на один уровень выше, в папку calculations.
Модули и пакеты 417
где находится модуль basic.ру.
Особенности запуска модулем
Файл с расширением .ру может выполнять две принципиально разные роли: модуль (импортирован) или программа (запущен напрямую).
Python автоматически создает и инициализирует специальную встроенную переменную____name___, значение которой зависит от способа запуска модуля,
о name_____ == main модуль запущен напрямую.
о __name___ == "имя_ модуля" - модуль импортирован другим модулем.
Условие if___name___== "__main__": позволяет создать блок кода, который
будет выполнен только в том случае, если файл запущен как основная программа, и будет проигнорирован, если файл импортируется как модуль
Это необходимо для разграничения функций, которые должны быть доступны при импорте и кода, который должен выполняться только при старте программы.
Например, модуль basic.ру может содержать не только функцию mean() для расчёта среднего арифметического, но и пример работы этой функции, который будет выведен на экран, только если этот модуль запущен напрямую:
#	caLcuLations/basic.ру
def mean(ciata):
numbers = list(map(float, data)) return sum(numbers) / len(numbers)
#	Этот код выполнится только при прямом запуске basic.ру if __паше_ == "_main__
result = теап([1л 2, 3, 4, 5])
print(f"CpeflHee арифметическое чисел 1, 2, 3, 4 и 5: {result})
Этот подход позволяет создавать модули, которые содержат полезные функции и при этом имеют встроенный механизм для их демонстрации при прямом запуске, не вмешиваясь в основной ход программы при импорте.
Примерь*
Пример 1. Расчёт точки безубыточности
Проект имеет следующую структуру:
Модули и пакеты 418
project/ I-— main.py I-— finance /
— ____init__. py
— stats.py
Функция get_break_even() для расчёта точки окупаемости находится во вложенном модуле stats.ру в пакете finance:
#	finaneе/stats py
def get_break_even( big_cost: int | float, price: int | float, cost_per_item: int | float
) -> int | None: II tl II
Рассчитывает точку окупаемости - сколько единиц товара нужно продать, чтобы полностью вернуть big_cost.
Параметры:
bigcost: Большая первоначальная трата (напр., оборудование), price: Цена продажи одной единицы товара.
cost_per_item: Себестоимость материален для одной единицы товара.
позврашает:
Минимальное количество единиц для окупаемости или None. II II II
#	Чистый доход с одной проданной единииы profit_per_item = price - cost_per_item
if profit_per_item <= 0: return None
#	Делим общие расходы на чистый доход с единицы и округляем вверх return round(big_cost / profit _per_item + 0.5)
Эта функция импортируется в запускаемый файл main.py с помощью абсолютного импорта и используется для расчёта точки безубыточности производства булочек:
# main.py
from finance.stats import get_break_even
big_cost = 120000 # Всего потрачено на организацию производства булочек price = 120 # Цена продажи одной булочки
costperitem = 60 # Себестоимость одной булочки
break_even_point = get_break_even(big_cost, price, cost_per_item)
print(f’Нужно предать {break_even_pcint} булочек )
Модули и пакеты 419
Вывод:
Нужно продать 2000 булочек
Пример 2. Нас тройки генерации отчёта
Проект имеет следующую структуру:
reporting/ — ____init__.ру
— settings.ру — generator.ру
Модуль settings .ру хранит настройки для создания отчёта:
#	reporting/settings.ру
report_title = "Ежемесячный финансовый отчет"
separator = "=" * 40
Переменные report_title с названием отчёта и separator с разделителем используются для создания заголовка отчёта в функции generate_header() в модуле generator.ру:
#	reporting/generator.py
from .settings import renort_title, separator
def generate_header(month: str) -> str: ......Генерирует заголовок отчёта.
Параметры:
month: Название месяца.
Зозвращает:
Строка с заголовком для отчёта. • I I» II
header = f"{separatorj-\n{report_titlej - {month.upper()}\n{separator}" return header
if ___name_ == "_main__ :
# Код для демонстрацииj если generator.ру запущен напрямую print(generate_header( Ноябрь'))
Если модуль generator .ру запущен напрямую, то выполняется код в блоке if __name___ == "___main___и на экран выводится пример заголовка отчёта за
ноябрь.
Модули и пакеты 42ft
Вывод:
Итоги
Модуль - это файл с расширением . ру. Все переменные, функции и классы из одного модуля можно использовать в других модулях.
J Скрипт - это файл с расширением .ру. предназначенный для запуска.
J Пакет - это набор связанных модулей, которые совместно обеспечивают определённую функциональность.
J Библиотека - это набор готового кода, который можно использовать повторно.
S Значение переменной___name__для каждого модуля зависит от способа его
запуска: оно равно "_main__", если модуль запущен напрямую, или сов-
падает с именем модуля, если он импортирован другим модулем.
Таблица 23 - Способы импорта модулей и пакетов
Импорт	Описание
import имя_модуля	Импорт модуля
from имя_модуля import...	Выборочный импорт элементов модуля
from имя_модуля import *	Импорт всего содержимого модуля
from имя пакета.имя модуля import ...	Абсолютный импорт модуля через указание полного пути к нужному модулю от корневого пакета
from . имЯ-Модуля import ...	Относительный импорт модуля, который находится на то же уровне с текущим файлом
from .. имя_модуля import	Относительный импорт модуля, который
• • •	находится на один уровень выше текущего модуля
Задания для самопроверки
1.	Как классический импорт (import имя_модуля) помогает избежать конфликтов имен?
Модули и пакеты 421
2.	Какие значения принимает встроенная переменная_name_в модуле,
если он запущен напрямую и, если он импортирован другим модулем?
3.	Создайте в одной папке файлы data_utils.ру и main.py. В модуле data_utils.ру напишите функцию clean_text(text). которая принимает строку text и возвращает эту же строку без лишних пробелом по краям и преобразованную в верхний регистр. В запускаемом файле main.py импортируйте весь модуль data_utils.ру и вызовите функцию clean_data() для строки " Данные бывают РаЗнЫе " и выведите результат на экран.
4.	Измените файл main. ру из предыдущего задания и импортируйте модуль data_utils.ру через псевдоним du. Вызовите функцию clean_data() через псевдоним для строки " Чёрные, белые, красные " и выведите результат на экран.
5.	Создайте модуль my_math.py с функцией add(a, б), которая принимает два числа а и b и возвращает результат сложения этих чисел. В этом же модуле создайте функцию subtract(a, b), которая принимает два числа а и b и возвращает результат вычитания числа b из числа а. В файле main.py используйте команду вида from ... import ... для импорта функции add() и выведите на экран результат её вызова для чисел 10 и 5.
6.	2 МОДУЛЬ MATH ДЛЯ МАТЕМАТИЧЕСКИХ ВЫЧИСЛЕНИЙ
Вместе с установкой интерпретатора Python вы получаете большой набор готовых инструментов, собранных в стандартной библиотеке Python. Модули, входящие в неё. считаются неотьемлемой частью языка, что позволяет не устанавливать дополнительные пакеты и решать самые разные задачи: математические вычисления, генерация случайных чисел, работа с датой и временем, и много другое.
Среди всего многообразия модулей стандартной библиотеки модуль math является незаменимым помощником, когда речь заходит о более сложных математических вычислениях, которые выходят за рамки базовых арифметических операций, рассмотренных ранее.
Для использования функций и констант из этого модуля его следует импортировать
import math
Модули и пакеты 422
Константы
Модуль math предоставляет несколько полезных математических констант.
Таблица 24- Математические константы в модуле math
Константа	Описание	Значение
math.pl	Число л (пи) - отношение длины окружности к ее диаметру	3.141592653589793
math.e	Число е (Эйлера) - основание натурального логарифма	2.718281828459045
math.inf	Положительная бесконечность	4-00
math.nan	С англ, not a number - «не число»	пап
Числа nialh.pi и malh.e
Константы math.pi и math.e являются обычными числами с плавающей точкой (float):
print(f"Площадь круга с радиусом 5 равна {math.pi * 5 ** 2:.2f}")
# Вывод: Площадь круга с радиусом 5 равна 78.54
print(f"Число Эйлера равно (math.e-.2f}")
# Вывод: Число Эйлера равно 2.72
С ними можно производить вычисления, сравнивать с другими числами и так далее.
Бесконечность
Положительная бесконечность math.inf, которая также создаётся конструкцией float("inf"), используется для представления очень больших значений, например, при сравнении чисел:
print(math.inf > 99 ** 999)
#	Вывод: True
Можно также использовать -math.inf для отрицательной бесконечности: print(-math.inf < -99 ** 999)
#	Вывод: True
Не число
«Не число» math, пап, которое также создаётся конструкцией float ("пап"), используется для представления результата некорректных математических
Модули и пакеты 423
операций. Главная особенность math.пап заключается в том, что оно не равно ничему, даже самому себе. Это помогает отличить его от обычных чисел, включая ноль и бесконечность:
print(math.nan == math.nan) # Вывод: FaLse print(math.nan < math.inf) # Вывод1 FaLse
Возведение в степень и вычисление корня
Хотя оператор ** позволяет возводить в степень и извлекать корень, модуль math предоставляет отдельные функции для этих операций.
Таблица 25 - Функции возведения в степень и вычисления корня в модуле math
Функция
Описание
Возврата- Аналогич-
емое зна- ное выраже-
math.sqrt(x)
math.pow(x, у)
чение ние
Возвращает квадратный ко- Число с х ** 0.5 рень из числа х	плавающей
Возводит число х в степень у точкой х ** у
Извлечение корня
Функция math.sqrt() является специализированной функцией для вычисления квадратного корня. Она более читабельна для математических формул, чем использование конструкции х ** 0.5:
print(math.sqrt(al)) # Вывод: 9.0 pnnt(81 ** 0.5) # Вывод: 9.0
Аргумент этой функции должен быть неотрицательным числом. Попытка извлечь корень из отрицательного числа приведет к ошибке ValueError, так как результатом будет комплексное число, с которым модуль math не работает.
Возведение в степень
Основное отличие функции math.powQ от оператора ** и встроенной функции pow() заключается в том. что она всегда возвращает результат типа float.
Модули и пакеты 424
print(math.pow(2, 3))
# Вывод: 8.0 print(2 ** 3)
# Вывод: 8 print(pow(2, 3)) # Вывод: 8
Также в отличие от встроенной функции pow(). функция из модуля math не может принимать третий аргумент для вычисления остатка от деления.
Округление чисел
Как мы уже говорили, для случаев, когда число ровно посередине между двумя ближайшими значениями (например. 2.5), встроенная функция round() использует банковское округление, то есть значение округляются в сторону ближайшего чётного числа. Однако модуль math расширяет возможности округления чисел в Python.
Таблица 26 - Функции округления чисел в модуле math
Функция Описание	Возвращае-
мое значение
rnath.ceil(x) Возвращает наименьшее целое число, кото- Целое число рое больше или равно х (округление вверх) math .floor(х) Возвращает наибольшее целое число, которое меньше или равно х (округление вниз) math. trunc(x) Отбрасывает дробную часть числа х, оставляя
только целую часть
Функция math.ceil() округляет число в большую сторону, а функция math.floor(), наоборот, в меньшую. Даже их названия в переводе с английского означают ceiling - потолок м floor- пол. то есть округление вверх и вниз:
print(math.ceil(3.5)) # вывод: 4
print(math.fLoor(3.5))
#	Вывод: 3
При этом результат обеих функций не зависит от того, является число чётным или нечётным.
Функция math.trunc() просто отбрасывает дробную часть числа: print(math.t rune(7.3))
Модули и пакеты 425
#	Вывод: 7
Факториал
Мы уже рассматривали факториал числа п как пример рекурсивной функции, которая возвращает произведение всех целых чисел от 1 до этого числа п включительно:
def factorial(n: int) -> int:
#	Базовый случай: если п оавно 0 или 1, возвращаем 1 и прекращаем рекурсию if п == 9 <г n == 1: return 1
#	Рекурсивный случай: п! = п * (п - 1)! return n * factorial(п - 1)
Однако мы можем не писать эту функцию, а импортировать её из модуля math:
print(math.factorial(3)) # вывод: б
При этом функция math.factorial() работает только с целыми неотрицательными числами.
Логарифмы и экспонента
Логарифм является обратной операцией к возведению в степень. Например, так как 2 ' = 8, то log28 = 3 (читается как «логарифм 8 по основанию 2»). То есть это показатель степени, в которую нужно возвести основание, чтобы получить заданное число.
Модуль math предоставляет несколько функций для вычисления логарифмов по разным основаниям, в том числе по основанию е (числу Эйлера). Такой логарифм называется натуральным логарифмом, и в математике его принято обозначать как In х.
Если говорить о натуральных логарифмах, то нельзя не упомянуть об экспоненте ех, которая является его обратной функцией, и для расчёта которой модуль math также предоставляет отдельную функцию.
Модули и пакеты 426
Таблица 27 - Логарифмы и экспонента в модуле math
Функция	Описание	Возвращаемые значение	Примеры
math.1о§(хл	Возвращает логарифм числа х по ос-	Число с	1о£з 81 = 4
base^math.e)	нованию base Если основание не за-	плаваю-	In е = 1
	дано, то - по основанию числа Эй-	щей точ-	
	лера math.e (натуральный логарифм)	кой	
math.log2(x)	Возвращает логарифм числа х по основанию 2 (двоичный логарифм)		log2 16 = 4
math.Iogl0(x)	Возвращает логарифм числа х по основанию 10 (десятичный логарифм)		1g 100 = 2
math,exp(x)	Возвращает экспоненту от числа х, то есть возводит число Эйлера math.e в степень х		е2 « 7.399
Функция math.]og() может вычислить логарифм по любому основанию, в том числе, 2. 10 или любое другое: pr'int(math.log(27, 3)) # Вывод: 3.0 print(math.]og(8, 2)) # Вывод 3.0 print(math.log(math.e)) # Вывод: 1.0
Функции math.]ogl0() и math.log2() являются частными случаями функции math.log() и позволяют не указывать нужное основание, а сразу вычислить двоичный или десятичный логарифм: print(math.logl0(10)) # Вывод: 1.0 print(math.Iog2(32)) # Вывод: 5.0
Функция math.exp() вычисляет экспоненту от числа, то есть возводит число Эйлера (math.e) в переданную степень:
print(math.exp(l))
#	Вывод: 2.718281828459045
При этом экспонента является обратной функцией для натурального
Модули и пакеты 427
логарифма. То есть In е' = х и е1" = х:
print(math.log(math.exp(2)))
it Вывод: 2.0
print(math.exp(math.]og(2)))
#	Вывод: 2.0
Тригонометрия
Тригонометрия изучает углы, стороны и их соотношения в треугольниках. Модуль math предоставляет основные функции для выполнения таких вычислений.
Рисунок 23. Тригонометрическая окружность
При работе с тригонометрическими функциями в Python (и в большинстве языков программирования) углы по умолчанию измеряются в радианах, а не в градусах. Эти две единицы измерения углов связаны с окружностью: полный оборот равен 360° или 2тг радиан, поэтому 1 радиан равен Зби°/2тг ® 57.3°, а угол 45° соответствует тт/4 радиан.
Для перевода между этими системами модуль math предоставляет две функции:
о math. radians (degrees) - переводит градусы degrees в радианы.
о math.degrees(radians) - переводит радианы radians в градусы.
Так мы можем убедиться, что угол 180° равен числу д, а угол п/2 соответствует 90°:
Модули и пакеты 428
print(math.radians(189))
#	Вывод: 3.141592653589793
print(math.degrees(math.pi / 2))
#	Вывод: 90. 0
Основные тригонометрические функции
Основные тригонометрические функции угла рассчитываются как соответствующее соотношение сторон в прямоугольном треугольнике, полученном при опускании перпендикуляра из точки на окружности до оси абсцисс.
Таблица 28 - Основные тригонометрические функции в модуле math
Функция	Математическая запись	Название	Описание	Возвра- щаемое значение
math.sin(x)	sin (x)	Синус	Отношение	противолежа- Число с щего катета угла х к г и поте- плаваю-нузе	щей	точ-
math.cos(x)	cos (x)	Коси- нус	Отношеиие прилежащего кой катета угла х к гипотенузе
math,tan(x)		Тан- генс	Отношение противолежащего катета угла х к прилежащему
Все эти функции принимают угол в радианах: print(math.sin(math.radians(30)))
# Вывод: 0.49999999999999994
print(math.cos(math.pi))
# Вывод: -1.0
print(math.tan(math.pi / 4))
if Вывод: 0.9999999999999999
Из-за особенностей работы с числами с плавающей точкой в Python тригонометрические функции могут возвращать значения с минимальной погрешностью. Например, синус угла 30° на самом деле равен ровно 0.5, а тангенс угла и/4 равен 1.
Обратные тригонометрические функции
Если основные функции находят соотношение сторон по углу, то обратные функции решают обратную задачу: они находят угол по известному Модули и пакеты 429
соотношению сторон. Эти функции также часто называют аркфункциями.
Таблица 29 - Обратные тригонометрические функции в модуле math
Функция	Математическая запись	Название	Описание	Во {вращаемое значение
math.asin(x)	arcsin (x)	Арксинус	Возвращает угол в радианах, синус которого равен х	Число с плавающей точкой
math.acos(x)	arccos (x)	Арккосинус	Возвращает угол в радианах, косинус которого равен х	
math.atan(x)	arcty^x)	Арктангенс	Возвращает угол в радианах, тангенс которого равен х	
Значение угла х для функций нахождения арксинуса mat h. as in () и арккосинуса math.acos() должны лежать в пределах от -1 до 1 включительно, в то время как функция арктангенса math.atan() может принимать любое значение: print(math.asin(3.5)) # Вывод: 0.52359S7755982989 print(math.ас os(-1)) # Вывод: 3.141592653589793 print(math.atar(math.sqrt(3))) # Вывод: 1.0471975511965976
Длина гипотенузы
Для вычисления длины гипотенузы прямоугольного треугольника с катетами х и у в модуле math есть функция math.hypot(x, у):
print(math.hypot(3, 4)) # Вывод: 5.0
Фактически это вычисление гипотенузы по теореме Пифагора у[х2 +у2, однако эта функция более устойчива к ошибкам округления, чем ручное вычисление math.sqrt(x ** 2 + у ** 2), особенно при очень больших или очень маленьких значениях х и у.
Модули и пакеты 430
Примеры
Пример 1. Расчёт расстояния между двумя точками
Определение расстояния между точками с координатами (xt; ух) и (х2; у2) в двумерном пространстве (например, на карте в игре) сводится к применению теоремы Пифагора:
d = /(х2 - *i)2 + (Уг - У1)2
Здесь:
о (х2 — %i) - длина одного катета (разница по горизонтали);
о (у2 — yj - длина второго катета (разница по горизонтали);
о d - гипотенуза (искомое расстояние).
Функция calculate_distance() принимает два кортежа с координатами точек и вычисляет расстояние между ними с помощью функции math. sqrt(): import math
def calculare_distance(pointl: tuple, point2: tuple) -> float: """Рассчитывает расстояние между двумя точками.
Параметры:
pointl: Координаты первой точки в виде кортежа (х, у).
point2: Координаты второй точки в виде кортежа (х, у).
Возвращает;
Расстояние между топками pointl и point2.
II II II
# Распаковка кортежей
Х1, у1 = pointl
х2, у2 = point2
# Квадратный корень из суммы квадратов
return math.sqrt((x2 - xl) ** 2 + (у2 - yl) ** 2)
# Представляем точки как кортежи point_a = (2.0, 5.0) # (xl, yl) point_b = (10.0, 11.0) # (х2, у2) print(f"Точка A: {point_a}, Точка В: (pointb) )
distance = calculate_distance(point_a, point Ja) print(f Расстаяние между точками: {distance::.2f}’)
Модули и пакеты 431
Обратите внимание, что пусть функции (и классы) следует отделять друг от друга и от остального кода двумя пустыми строками, то после строки с импортом достаточно оставить одну пустую строку.
Вывод:
Точка А: (2.0, 5.0), Точка В: (10.0, 11.0) Расстояние между точками: 10.00
Пример 2. Обработка результатов вычислений
Математические вычисления часто требуют расчёт среднего арифметического. Для этого сумма элементов списка делится на его длину, однако длина пустого списка равна нулю, поэтому функция safe_mean() в гаком случае возвращает константу math.пап, означающую «не число»: import math
def safe_mean(data: list[int, float]) -> float: .......Вычисляет среднее арифметическое списка чисел data.
Параметры:
data: Список чисел. Может быть пустым.
Зоньращает
NaN для пустого списка. II II II if not data:
# Пустая последовательность: возвращаем math.пап ("Не число") return math.nan
return sum(data) / len(data)
#	Обычные данные
datal = [IG, 20, 30]
print (f Среднее арифметическое списка {datal}: {safe_mean(datal)}')
#	Пустой список
data2 = []
print(f Среднее арифметическое списка {data2}: {safe_mean(data2)}")
Вывод:
Среднее арифметическое списка [10, 20, 30]: 20.0
Среднее арифметическое списка []: пап
Модули и пакеты 432
Пример 3. Насчёт суммы инвестиций с непрерывным начислением процентов
При непрерывном начислении процентов проценты начисляются на сумму вклада за бесконечно малые промежутки времени, то есть капитализация происходит постоянно. Это означает, что процентная ставка применяется на пост оянно растущую сумму, что приводит к более быстрому росту дохода, чем при периодической капитализации. Общая накопленная сумма на конец периода с процентами определяется по формуле:
S = е/ т -Р
Здесь:
о е - число Эйлера;
о / - ставка непрерывных процентов в долях;
о Т - срок вложения;
о Р- сумма первоначальных вложений.
Функция continuous_compound() использует данную формулу и рассчитывает общую накопленную сумму, используя функцию math.exp
import math
def continu<jus_compound(
principal: float, rate_fraction: float, time_years: float
) -> float: II M II
Рассчитывает финальную сумму при непрерывном начислении процентов.
Параметры:
principal: Сумма первоначальных вложений.
rate_fraction: Ставка непрерывных процентов в долях.
time_years. Срок вложения.
Возвращает:
Общая накопленная сумма на конец периода с процентами. II II II
return math.exp(rate_fraction * time_years) * principal
initial_investment = 1Э00ИИ.0 tt Начальные вложения print(f"Начальная сумма: {initial_investment} руб. )
annual_rate =0.15 # Ставка 15%
print(f"Ставка: {annual_rate * 160} %' )
Модули и пакеты 433
time_borizon = 1(3.0 # Срок 1в лет
Final = continuous_compound(initial_investmentj annual_rate, time_horizon) print(f"Через {time_horizon} лет сумма составит {final:.2f) руб. )
Вывод:
Начальная сумма: 100000.0 руб.
Ставка: 15.0 %
Через 10.0 лет сумма составит 448168.91 руб.
Итоги
J Модуль math предоставляет математические функции и константы для выполнения вычислений.
J В модуле math определены константы для числа Пи (math.pl), числа Эйлера (math.e), а также для бесконечности (math.inf) и не числа (math.пап).
J Модуль math предоставляет альтернативу для оператора возведения в степень ** - функцию math.pow(), а также функцию math.sqrt() для вычисления квадратного корня.
Для округления числа в большую сторону используется функция math.ceil(), в меньшую - math.floor(), а для отбрасывания дробной части числа - math.trunc().
J Функция math. factorial () вычисляет факториал числа, то есть произведение всех целых чисел от 1 до этого числа включительно.
S Для вычисления логарифма предназначена основная функция math.log(), которая позволяет вычислять логарифм по любому основанию, но также есть специальные функции math.log2() и math.logl0(), вычисляющие логарифм по основаниям 2 и 10 соответственно.
S Функция math .ехр() возводит число Эйлера в степень переданного числа.
J При работе с тригонометрическими функциями в Python углы измеряются в радианах, а не в традусах. Для перевода градусов в радианы используется функция math.radians(), а радианов в градусы - функция math.degrees().
J Функции math.sin(), math.cos(), math.tan() вычисляют синус, косинус и тангенс угла соответственно.
S Функции math.asin(), math.acos() и math.atan() вычисляют арксинус, арккосинус и арктангенс угла соответственно.
Модули и пакеты 434
Задания для самопроверки
1.	Какое основание по умолчанию используется функцией math. log(). если второй аргумент base не указан? Как этот логарифм называется в математике?
2.	Округлите число 2.5 сначала в большую, а затем в меньшую сторону, используя функции модуля math. Выведите оба округлённых числа на экран.
3.	Напишите функцию calculate_area(radius). которая принимает радиус radius и использует константу math.pi для вычисления площади круга по формуле S = я • г2. Выведите на экран результат вызова функции для радиуса radius = 4, округлив его до двух знаков после запятой.
4.	Напишите функцию get_trigcnometry(angle_degrees), которая принимает угол angle degrees в градусах. Функция должна вычислить значения синуса, косинуса и тангенса этого угла (подумайте об единицах измерения) и вернуть полученные значения в виде словаря. Вызовите эту функцию для угла 30 градусов и выведите результат на экран.
5.	Вычислите значение следующего выражения, используя функции модуля math.
е1у 5 + \ 12 4- cos 0.3
Выведите результат на экран, округлив его до трёх знаков после запятой.
6.3 МОДУЛЬ RANDOM ДЛЯ ГЕНЕРАЦИИ СЛУЧАЙНЫХ ЧИСЕЛ
Случайные числа в программировании играют важную роль во многих процессах, например, при симуляции броска кости, генерации случайных входных данных или моделировании процессов.
Однако компьютер не способен создать по-настоящему случайное число, так как он является детерминированной системой, то есть при одних и тех же входных данных выдаёт один и тот же результат. Поэтому в программировании используются псевдослучайные числа, вычисляемые на основе начального значения, которое называют seed (с англ. - зерно).
Для работы с псевдослучайными числами в Python исггользуется модуль random. Для исггользования функций из этого модуля его следует импортировать: import random
По умолчанию модуль random использует текущее системное время как
Модули и пакеты 435
начальное значение. Поскольку время постоянно меняется, этого достаточно для большинства практических задач. Однако мы можем установить это начальное значение вручную с помощью функции random. seed():
random.seed(5)
Это полезно для отладки и воспроизводимости результатов, так как тогда генератор всегда будет выдавать одну и ту же последовательность псевдослучайных чисел.
Функции ।операции чисел
Функции генерации чисел являются основой модуля random. Они создают базовые случайные значения, которые затем используются для всех остальных операций (выбора элементов, перемешивания и т.д.). Также они используют равномерное распределение, то есть каждое число в заданном диапазоне имеет равную вероятность быть выбранным.
Таблица 30 - Функции генерации чисел в модуле random
Функция	Описание	Возвра-	Диапазон
random.random()	Возвращает случайное число с	щаемое значение Число с	[0.0. 1.0)
random.uni-	плавающей точкой от 0 до 1, не включая 1 Возвращает случайное число с	плавающей точкой	[min, max]
form(min, max) ran-	плавающей точкой в диапазоне от min до max включительно Возвращает случайное целое	Целое	[start,
dom. randrange(	число из последовательности.	число	stop)
start=0, stop, step-1) random.rand-	которую можно создать с помощью функции range () с этими же параметрами start, stop и step Возвращает случайное целое		[min. max]
int(min, max)	число в диапазоне от min до max		
включительно
Модули и пакеты 436
Напомним, что в математике круглой скобкой обозначается открытый интервал (значение не включается), а квадратной - закрытый интервал (значение включается).
Генерация чисел с плавающей точкой
Случайные числа с плавающей точкой (float) генерируют функции random, random () и random.uniform().
Функция random.random() возвращает число, которое больше или равно О, но строго меньше 1. Это может использоваться для работы с вероятностью, например, для создания события с вероятностью 1 %:
if random.random() <= 0.01:
print("3To легендарный персонаж!' ) else:
print( Это дубина переговоров...")
Функция random.uniform() не ограничивает диапазон, из которого выбирается число, а позволяет задать его самостоятельно:
print(random.uniform(2J 2.5))
it Вывод: 2.0850676713833898
Причём обе границы min и max включаются в возможный результат.
Генерация целых чисел
Случайные целые числа (int) генерирую) функции random. randrange() и random.randint().
Функция random.randrange() принимает те же параметры, что и функция range(). start, stop и step и возвращает случайное число из последовательности, которая могла бы быть создана функцией range() с этими параметрами: print(random.randrange(2, 8, 2)) it Вывод: 2
Число start включается в возможный результат, а число stop - нет. поэтому здесь целое число выбирается из последовательности всех чётных чисел, больше или равных 2, но строго меньше 8.
Как и в функции range(). параметры start и step не являются обязательными. тогда число выбирается из последовательности от 0 до stop с шагом 1: print(random.randrange(10)) it Вывод: 7
Вызов функции	random, randint (а,	Ь) эквивалентен вызову
Модули и пакеты 437
random.randrange(a, b + 1), то есть она возвращает случайное целое число из диапазона целых чисел от а до b включительно:
print(random.randintCl, 6))
#	Вывод: 6
Функции для работы с последовательное!ими
Помимо генерации одиночных чисел, модуль random предоставляет функции для работы с последовательностями, то есть упорядоченными коллекциями. Они позволяют реализовать случайный выбор одного или нескольких элементов, а также изменение порядка элементов последовательности.
Таблица 31 - Функции для работы с последовательностями в модуле random
Функция	Ой и сан не	Возвращаемое значение
ran-	Возвращает случайный элемент из	Элемент последова-
dom. choice(seq)	последовательности seq	тельности
random.sam-	Возвращает список длиной к из по-	Список из к элемен-
ple(seqj k)	следовательности seq(без повторов)	тов
ran-	Возвращает список длиной к из по-	Список из к элемен-
dom. choices(seqj	следовательности seq (с повторами	тов
k=l,	согласно весам weights, если они	
weights=None)		
	указаны)	
random.shuf-	Перемешивает элементы в последо-	None, так как изме-
fle(seq)	вательности seq	няет исходную по-
следовательность
Эти функции предназначены для работы со строками, кортежами и списками, однако строки и кортежи неизменяемы, поэтому в них нельзя перемешать символы с помощью функции random.shuffle(). Также модуль random не работает со множествами, так как они не упорядочены, и со словарями, так как к их элементам нельзя обратиться по индексу.
Выбор случайного элемента
Функция random.choice() возвращает один случайный элемент из непустой последовательности, которой может быть строка, список или кортеж:
Модули и пакеты 438
print (random, choice (["Орёл"., "Решка1 J)) # Вывод: Орел
Выбор нескольких случайных элементов
Выбрать несколько случайных элементов из последовательности позволяют функции random.sample() и random.choices(). Кроме последовательности seq они принимают целое число к - количество элементов, которые необходимо выбрать. Разница между этими двумя функциями заключается в том, что функция random. samp]е() не может выбрать один и тот же элемент повторно, поэтому число к не должно превышать длину последовательности seq, а функция random. choicest) может выбрать один и тот же элемент несколько раз;
box = [ 'Белый шар' , "Красный шар , "Чёрный шар"]
print(random.sample(box, 2))
#	Вывод: ['Белый шар', 'Красный шар']
print(random.choices(box, k=5))
#	Вывод: ['Красный шар', 'Черный шар', 'Черный шар', 'Чёрный шар', 'Белый шар' ]
При этом функция random.choices() принимает число к только как именованный аргумент, и по умолчанию он равен 1.
И если остальные рассмотренные функции модуля random выбирают элемент с равной вероятностью, то функция random.choices() позволяет изменить это с помощью параметра weights, в который можно передать список весов, определяющих вероятность выбора элемента.
Например, мы моделируем добычу ресурсов в игре. Пусть радужная руда является редким ресурсом и имеет базовый вес 1. С большей вероятностью (в 9 раз чаще) игрок добудет простую медную руду, поэтому её вес равен 9. То есть вероятность добычи радужной руды составляет 10%. а медной - 90%:
ore = ["Радужная руда", "Медная руда ]
print(random.choices(ore., weights=[l, 9], k=5))
#	Вывод: ['Медная руда', 'Медная руда', 'Медная руда', 'Медная руда', 'Радужная руда' ]
Тогда при добыче руды (выборе 5 элементов из исходного списка) чаще всего будет добыта именно "Медная руда".
Перемешивание элементов
Функция random.shuffle() изменяет порядок элементов в последовательности, поэтому передаваемая ей последовательность должна быть изменяемым объектом, например, списком:
Модули и пакеты 439
cards = ["Джокер", "Бубновый король", "Пиковый валет"] random, shuffle(cards) print(cards)
#	Вывод: [‘Бубновый король'Джокер', 'Пиковый валет']
Она изменяет исходный список и возвращает None.
Примеры
Пример I. Моделирование броска двух игральных костей
Программа моделирует бросок двух игральных костей 10000 раз и подсчитывает, как часто выпадает сумма 7: import random
target_count = О
#	Имитируем броски
for _ in range(10000):
rolll = random.randint(l, б)
roll2 = random.randint(l, 6)
currentsum = rolll + roll2 if current_sum == 7: target_counc += 1
print(f Сумма 7 выпала {target_count} раз за 10000 бросков") print(f"Теоретическая вероятность: {6 / 36 * 100:.2f}%') probability = (target_count / 10B0G) * 100
print(f Экспериментальная вероятность: {probability .2f}%")
Вывод:
Сумма 7 выпала 1680 раз за 10000 бросков
Теоретическая вероятность: 16.67%
Экспериментальная вероятность: 16.80%
Пример 2. Моделирование системы л^тбоксов
Лутбоксом называют виртуальный предмет в игре, который содержит случайный набор игровых предметов, и у каждого предмета есть своя вероятность выпадения.
Программа моделирует получение предмета из лутбокса 1000 раз и подсчитывает, сколько раз выпал каждый предмет. Для этого используется класс Counter из встроенного модуля collections. Он предназначен для решения задачи подсчета неизменяемых объектов (например, строк или чисел) в коллекции, такой как список. Когда вы передаете список в Counter(), он возвращает словарь,
Модули и пакеты 440
где ключами являются уникальные элементы из списка (например, "Легендарный меч"), а значениями - количество раз. которое этот элемент встретился в списке: import random from collections import Counter # Встроенный класс для подсчета элементов
items = ["Легендарный меч", "Редкий лук", "Обычный щит", "Эликсир здоровья"] # Веса как вероятности
weights = [0.5, 4.5, 15.0, 80.0]
#	Выбираем N элементов с учетом заданных весов
results = random.choices( population=items, weights=weights, k=1000
)
#	Подсчитываем результаты с помощью Counter
drop_counts = Counter(results)
print(Г’Лутбок открыт 1000 раз:")
for item, count in drop_counts.most_common():
# Вычисляем процент выпадения
percentage = (count I 100G) * 100
print(f {item} получен {count} раз (вероятность {percentage:.2f}%)")
Вывод:
Лутбок открыт 1000 раз:
Эликсир здоровья получен 814 раз (вероятность 81.40%)
Обычный щит получен 137 раз (вероятность 13.70%)
Редкий лук получен 47 раз (вероятность 4.70%)
Легендарный меч получен 2 раз (вероятность 0.20%)
Пример 3. Генерация случайною аватара
Функция generate_avatar() генерирует случайный аватар, выбирая одну случайную часть тела из каждой категории (голова, тело, ноги): import random def generate_avatar() -> dict[str, str]: """Генерирует аватар, выбирая случайный элемент из каждой категории.
Возвращает:
Словарь с элементами аватара. II II II
avatar_parts = {
"Голова": ["Шлем викинга", "Ковбойская шляпа’, "Кошачьи ушки ], "Тело": ['Броня', "Спортивная майка", Пижама"],
"Ноги": ["Ботинки", ’Кроссовки", "Ласты"]
Модули и пакеты 441
}
avatar_config = {}
for part_type, partslist in avatar_parts.items(): # выбираем один элемент из списка selected_part = random.choice(parts_list) avatar_config[part_typej = selected_part
return avatar_config
avatar] = generate_avatar()
print ( Vearap 1: )
for part, name in avatarl.items(): print(f"{part}. {name}')
avatar2 = generate_avatar()
print( ЛпАватар 2: )
for part, name in avatar2.items(): print(f {part}: {name}' )
Вывод:
Аватар 1: Голова: Шлем викинга Тело: Броня Ноги: кроссовки
Аватар 2: Голова: Ковбойская шляпа Тело: Спортивная майка Ноги: Кроссовки
Итоги
J В программировании используются псевдослучайные числа, вычисляемые на основе начального значения
J Модуль random предоставляет функции для работы с псевдослучайными числами.
По умолчанию модуль random использует текущее системное время как начальное значение, но это можно изменить с помощью функции гап-dom.seed().
J Случайное число с плавающей точкой от 0 до 1 возвращает функция random. random(), а из заданного диапазона - функция random.uniform().
J Случайное целое число из последовательности, создаваемой функцией range() возвращает функция random. randrange(), а из заданного
Модули и пакеты 442
диапазона - функция random.randint().
✓ Функция random.choice() возвращает случайный элемент из последовательности. а функция random, shuffle() перемешивает всю последовательность целиком.
J Функции random.sample() и random, choices() возвращают список заданной длины из последовательности, однако первая функция делает это без повторяющихся элементов, а вторая разрешает повторы.
Задания для самопроверки
1.	Почему числа, генерируемые компьютером, называются псевдослучайными? Что такое seed, и для чего используется функция random. seed()?
2.	Напишите функцию random temperature(min temp, max_temp), которая принимает минимальное min_temp и максимальное max_temp значение температуры и возвращает случайную температуру (число с плавающей точкой) в этом диапазоне. Используя эту функцию, получите значение от 18.2°С до 25.5°С и выведите его на экран.
3.	Дан список участников телешоу participants = ["Анна", "Борис", "Светлана", "Дмитрий", "Елена"]. Перемешайте этот список и выведите его на экран до и после перемешивания.
4.	Дан список из четырех направлений света directions = ["Север", “Юг", "Восток", "Запад"] Используйте одну из функций модуля random и выведите на экран случайное направление.
5.	Напишите функцию simulate_event(probability), которая принимает вероятность probability (в долях) и возвращает True с этой вероятностью и False в противном случае. В цикле for вызовите функцию simulate_event(0.2) 100 раз и подсчитайте, сколько раз было возвращено True (ожидается около 20). Результат выведите на экран.
6.4 МОДУЛЬ DATETIME ДЛЯ РАБОТЫ С ДАТОЙ И ВРЕМЕНЕМ
Многие программы должны учитывать дату и время, например, при регистрации нового пользователя, создании заказа в интернет-ма] азине или планировании каких-то событий.
Модули и пакеты 443
Модуль datetime ь Python является основным инструментом для работы с датой и временем. Он предоставляет классы, которые позволяют хранить и манипулировать датой и временем. Для этого в нём определены четыре основных класса:
о date - дата
о t ime - время
о datetime - дата и время
о timedelta - временной промежуток
Для использования этих классов их следует импортировать из модуля datetime:
from datetime import date, time, datetime, timedelta
Класс date
Класс date используется для представления даты без учета времени.
Таблица 32 - Класс date для работы с датой в модуле dateiime
Атрибуты	Методы
Атрибут Диапа-	Тип	Метод	Возвращае-
зон	мое значение
year-год	11,9999]	int date.today()- возвращает те- Объект date
month-месяц [1, 12]	кущую дату (без времени)
day-день	[1,31]	
Объект этого класса создаётся с помощью конструктора date(). который принимает обязательные параметры year (год), month (месяц) и day (день): date(year, month, day)
Значения параметров должны находиться в допустимых диапазонах: год от 1 до 9999, месяц от 1 до 12, а день зависит от месяца и года, например, 30 дней для апреля.
Давайте создадим новую дату:
current_ddte = date(2025, 11, 13)
print(current_date) # 2025-11-13
Или с помощью метода date.today() получим текущую дату (на момент
Модули и пакеты 444
выполнения программы):
current_date = date.today() print(current_date) if 2025-11-13
Атрибуты year, month и day экземпляра класса date позволяют получить год, месяц и день в виде целого числа:
print(currentdate.ypar) # 2025 print(current_date.month) # 11 print(currentdate.day) if 13
Класс time
Класс time используется для представления времени независимо от даты.
Таблица 33 - Класс time для работы со временем в модуле datetime Атрибут	Диапазон Тип
hour=0 - час	[0,23]	int
minute=0 - минута	[0, 59]
second=0 - секунда	[0, 59]
microsecond=0 - микросекунда	[0, 999999]
Объект этого класса создаётся с помощью конструктора time() с необязательными параметрами hour (час), minute (минута), second (секунда) и microsecond (микросекунда): time(hour, minute, second, microsecond)
Значения параметров должны находиться в допустимых диапазонах: час от О до 23. минута и секунда от 0 до 59 и микросекунда от 0 до 999999.
Если какой-то параметр не передан, то он инициализируется нулём: lunch_time = time(14, 30) print(lunchtime) if Вывод: 14:30'00
У каждого экземпляра класса time есть атрибуты hour, minute, second и microsecond, которые позволяют получить час, минуту, секунду и микросекунду в виде целого числа:
print(lunch_time.hour)
Модули и пакеты 445
#	Вывод: М print(luneh_time.minute)
#	Вывод: Зв
print(lunch_time.second)
#	Вывод: 0
Класс datetime
Класс datetime объединяет в себе функциональность классов date и time, представляя конкретный момент во времени.
Таблица 34 - Класс datetime для работы с датой и временем в модуле datetime
Атрибуты		Методы		
Атрибут	Диапазон	Тип	Метод	Во гврашаемое значение
year - год	[1.9999]	int	datetime.now()	Объект
month - месяц	[1, 12]		возвращает теку-	datetime
day - день	[1,31]		щую дату и время	
hour-0 - часы	[0, 23]		datetime.date()	-	Объект date
minute-0 - минуты	[0. 59]		возвращает дату из	
second-0 секунды	[0, 59]		объекта datetime	
microsecond-0	[0. 999999]		datetime.time()	Объект time
микросекунды			возвращает <ремя из объекта datetime	
Объект этого класса создаётся с помощью конструктора datetime(), который принимает все параметры классов date и time: datetime( year, month, day, hour=0, minute=0, second=0, microsecond-0 )
Все параметры даты являются обязательными, а времени - нет, и тогда они инициализируются нулями:
print(datetime(2025, 3, 14, 12, 30))
Модули и пакеты 446
# Вывод: 2025-03-14 12:30-00
print(datetime(2025, 10, 12)) # Вывод: 2025-10-12 00:00:00
Метод datetime.now() позволяет не задавать дату вручную, а получить текущую (для системы) дату и время: now = datetime.now() print(now) # Вывод: 2025-11-14 22:37:48.613549
У экземпляров класса datetime есть все атрибуты классов date и time, print(f"{now.year} год {now.month} месяц {now.day} день " f"{now.hour} часа {now.minute} минут" ) # Вывод: 2025 год 11 месйи 14 день 22 часа 44 минут
Кроме этого, объект datetime может быть разделен на компоненты даты и времени с помощью методов:
о datetime, date() - возвращает компонент даты как объект date о datetime.time() - возвращает компонент времени как объект time
meeting = datetime(2025, 12, 25, hour-12, minute=25)
meeting_date = meeting.date() print(meeting_date) # Вывод: 2025-12-25 print(type(meeting_date)) # Вывод: <cLass 'datetime.date’>
meeting_time = meeting time() print(meeting_time) # Вывод: 12:25:00 print(type(meeting_time)) tt Вывод: ccLass 'datetime, time '>
Класс limed el ta
Класс timedelta представляет собой продолжительность или разницу между двумя моментами времени (объектами date или datetime). В отличие от классов date или datetime, которые обозначают конкретный момент во времени, timedelta обозначает протяженность времени. Поэтому в этих класса имена параметров и атрибутов пишутся в единственном числе (без «8» на конце), а в классе timedelta - во множественном числе (с «8» на конце).
Модули и пакеты 447
Таблица 35 - Класс timedelta для работы с временным промежутком в модуле datetime
Атрибуты	Методы
Атрибут	Диапазон	Тин	Метод	Возвращаемое значение
days-0 - дни	[-999999999.	int	timedelta. to-	Число с плава-
	999999999]		tal_seconds()	ющей точкой
seconds=e - се-	[0, 86399]		возвращает об-	
кунды			щую продолжи-	
mi croseconds-0-	[0, 1000000]		тельность проме-	
микросекунды			жу гка в секундах	
Объект этого класса создаётся с помощью конструктора timedelta(). который принимает необязательные параметры days (дни), seconds (секунды), microseconds (микросекунды), milliseconds (миллисекунды), minutes (минуты), hours (часы) и weeks (недели). По умолчанию все параметры инициализированы нулём:
timedelta(
days-0,
seconds=0,
microseconds-0,
milliseconds-0,
minutes=0, hours^O, weeks=0 )
Класс timedelta хранит временной промежуток в днях, секундах и микросекундах, то есть все параметры, кроме days, seconds и microseconds, преобразуются в эти базовые единицы:
о 1 milliseconds = 1000 microseconds.
о 1 minutes = 60 seconds.
о 1 hours = 3600 seconds.
о 1 weeks = 7 days.
Благодаря этому мы можем передавать конструктору timedelta () как целые числа, так и числа с плавающей точкой:
holidays = timedelta(weeks=l.5, hours=21)
print(holidays)
Модули и пакеты 448
# 11 days, 9:00:00
Несмотря на то, что такой объект выводится в консоли как дни, часы, минуты и секунды, данные он хранит в атрибутах days, seconds и microseconds, которые позволяют получить дни. секунды и микросекунды в виде целого числа: print(holidays.days) # Вывод: 11 print(holiday$.seconds) # вывод: 32400 print(holidays.microseconds) # Вывод: 0
Также мы можем получить общую продолжительность временного промежутка в секундах с помощью метода timedelta.total_seconds():
print(holidays.total_seconds()) # Вывод: 982800.0
Арифметические операции над датой и временем
Модуль datetime позволяет не только хранить дату и время, но и выполнять с ними арифметические операции. Складывать и вычитать между собой можно только объекты одного типа: date с date и datetime с datetime. Объекты класса time нельзя складывать или вычитать, так как они не привязаны к конкретной дате.
Кроме того, объекты timedelta могут быть прибавлены к объектам date и datetime или вычтены из них.
Таблица 36 - Сложение и вычитание даты и времени в модуле datetime
Класс 1	Onepaiop	Класс 2	Результат
date	-	date	timedelta
datetime	•	datetime	timedelta
date или datetime	-	timedelta	date или datetime
date или datetime	+	timedelta	date или datetime
Сложение и вычитание даты и времени между собой
Вычитание из одной даты и времени другой вычисляет разницу между ними, то есть временной промежуток timedelta:
datel = date(202S, 10, 15)
date2 = date(2O25, 10, 13)
print(datel - date2)
Модули и пакеты 449
# Вывод: 2 days, 0-00:00
dtl = datetime(2025, 10, 15, hour=14)
dt2 = datetime(2025, 10, 13, hour=10, minute=15)
print(dtl - dt2)
#	Вывод: 2 days, 3:45:00
Сложение и вычитание даты и времени с временным промежутком
Также временной промежуток может быть вычтен из даты/времени или прибавлен к нему, что приведёт к сдвиганию даты назад или вперёд: date_obj = date(202e, 12, 14) timedelta_days = timedelta(days=2) print(dateobj - timedeltadays) # Вывод: 2020-12-12 (Сдвиг на 2 дня назад) print(date_obj + timedelta_days)
#	Вывод: 2020-12-16 (Сдвиг на 2 дня вперёд)
dt_obj = datetime(2025, 5, 4, hour=10, minute=5)
timedelta_weeks = timedelta(weeks=2) print(dt_obj - timedelta_weeks)
#	Вывод: 2025-04-20 10:05:00 (Сдвиг на 2 недели назад) print(dt_obj + timedelta_weeks)
#	Вывод: 2025-05-18 10:05:00 (Сдвиг на 2 недели вперед)
Результат сложения или вычитания объекта с t imedelta возвращает объект этого же класса: date или datetime
Операции сравнения даты и времени
Объекты date, time и datetime могут быть сравнены с помощью стандартных операторов сравнения. Сравнение начинается с наибольшего компонента, например, года в date и datetime, и переходит к меньшему, то есть месяцу, дню. часу (для datetime) и так далее: dtl = datetime(2025, 1, 1, hour=10) dt2 = datetime(2025, 1, 1, hour=9, minute=59) print(dtl > dt2) # Вывод: True
Сравнение останавливается при первом различии. Например, если года разные, остальные компоненты не проверяются.
datel = date(2025, 10, 2)
date2 = date(199P, 12, 5) print(datel < date2) # Вывод: False
Модули и пакеты 450
Преобразование даты
Работа с датами и временем редко ограничивается их хранением. Гораздо чаще требуется вывести их в понял ном человеку виде (в виде строки) или. наоборот. прочитать дату из файла или ввода пользователя (разобрать строку).
Также время часто хранится как одно большое число, называемое меткой времени (англ. - timestamp). Оно представляет собой количество секунд, прошедших с 1 января 1970 года, 00:00:00. Поскольку отсчет ведется от UTC (от англ. Coordinated Universal Time - Всемирное координированное время) метка времени Unix всегда одинакова для одного и того же момента времени, независимо от того, где находится пользователь. Это значительно упрощает хранение, сравнение и передачу данных о времени между разными часовыми поясами.
Таблица 37 - Методы для преобразования даты и времени в модуле datelime
Классы	Метод	Описание	Возвраща-
	Методы объекта	емое зна- чение
date. time. datetime	obj. strftime(format)	Преобразует объект ob j с датой и временем в строку в соответствии со строкой формата format	Строка
datetime	obj .timestampO	Преобразует объект datetime в метку времени Методы класса	Число с плавающей точкой
datetime	datetime.strptime(sJ -for- Преобразует строку s с mal:)	датой и временем в объект datetime в соответствии со ст рокой формата format	Объект datetime
datetime	datetime.	Преобразует метку fromtimestamp(timestamp)	времени timestamp в объект datetime	Объект datetime
Модули и пакеты 451
Преобразование даты и времени в строку и из строки
Метод obj. strftime() преобразует объект date, time или datetime в читабельную строку в соответствии с заданным шаблоном.
Метод вызывается на объекте даты и времени и принимает в качестве аргумента строку формата. Эта строка содержит обычные символы, которые будут скопированы как есть, и специальные коды, начинающиеся с символа процента. которые заменяются соответствующими значениями даты и времени.
Таблица 38 - Основные коды для формата даты и времени в модуле daletime		
Код	Описание Год	Пример
%Y	Год с веком (4 цифры)	2025
%у	Год без века (2 цифры) Месяц	25
%т	Месяц (01-12)	01
%В	Полное название месяца	November
%Ь	Сокращённое название месяца День недели	Nov
%d	День месяца (01-31)	IS
%А	Полное название дня недели	Thursday
7<м	День недели (0-6) - отсчёт начинается с воскресенья (0) Время	0
%Н	Час (24-часовой формат, 00-23)	22
%1	Час (12-часовой формат, 01-12)	10
%М	Минута (00-59)	30
%S	Секунда (00-59)	05
%р	AM (до полудня) или РМ (после полудня) - используется с %1	PM
Этот метод позволяет создать любой желаемый строковый формат из объектов с датой и временем
dt_obj = datetime(2025, 11, 16, 16, 10, 37)
format_dt = "%A, %d %B %Y. Текущее время:
result_dt = dt_oDj . strftime(-Format_dt)
print(result_dt)
# вывод: Sunday, 16 November 2025. Текущее время: 16.10:37
Модули и пакеты 452
date_obj = date(2025, 11, 16)
format_date = Тод ^Y, день года N^j. Номер недели; %W"
result_date = date_obj.strftime(format_date) print(result_date)
#	Вывод: Год 2025, день года N-320. Номер недели: 45
timeotij = time(16, 10, 37)
format_time = "Время: %1:%М %р. Микросекунды: %f"
result_time = time_obj. strftime(foi'mat_time) print(result_time)
#	Вывод: Время: 04'10 PM. Микросекунды. 000000
Метод класса datetime.strptime() выполняет обратную операцию: он преобразует строку, содержащую дату и время, в полноценный объект datetime Он принимает строку, которую нужно разобрать, и строку формата, которая должна точно описывать структуру исходной строки.
Названия методов strftime() и strptime() в модуле datetime в Python кажутся очень похожими, но названия этих методов являются сокращениями от фраз «string format time» и «string parse time». To есть метод strftime() форматирует строку, а метод strp-time() парсит или преобразует её в объект datetime
Для успешною преобразования сзрока формата должна быть идеальным зеркалом входной строки, заменяя только значения даты/времени на соответствующие директивы (%Y. %m и так далее):
date_string = 'Момент запуска: 16.11.25 в 13:55"
#	Формат должен точно соответствовать cmpoKej включая текст и разделители format_string = "Момент запуска: %d.%m_“iy в %Н:%М“ dt_obj = datetime.strptime(date_string, tormat_string) print(dt_obj)
#	Вывод: 2025-11-16 13:55:00
Любое несовпадение (лишний пробел, другой разделитель, отсутствие директивы) приведет к исключению ValueError.
Преобразование даты и времени в метку времени и из метки времени
Хотя для человека наиболее понятными являются форматы, такие как «1
Модули и пакеты 453
января 2025 года», программы часто предпочитают работать с датой и временем в виде одного большого числа (метки времени).
Метод datetime.timestamp() преобразует объект datetime в число с плавающей точкой (float), представляющее секунды с начала эпохи Unix (1970-01-01 UTC):
moment = aatetime(2025, 1, 1, 12, 0, 0)
ts_value = moment.timestampO
print(ts_value)
#	Вывод: 1735722000.0
Метод datetime.fromtimestampO выполняет обратное действие, преобразуя числовую метку времени обратно в объект datetime.
dt_from_ts = datetime.fromtimestamp(ts_value)
pri nt(dt_from_ts)
#	Вывод: 2025-01-01 12:00:00
Он возвращает объект datetime, который скорректирован под локальный часовой пояс компьютера, на котором выполняется программа.
Примеры
Пример 1. Обратный отсчет до запланированною события
Функция is_deadline(deadline) рассчитывает точное время, оставшееся до заранее установленной даты и времени deadline (например, до дедлайна проекта), и выводит результат в удобном для человека виде:
from datetime import datetime
def is_deadline(deadline: datetime) -> None: ......Рассчитывает время, оставшееся до даты deadline.
Параметры: deadline: Дата и время события.
11 It II
#	Вычисляем разницу (timedeLta) time_left = deadline - datetime.now()
#	Проверяем, наступило ли событие if time_left.total_seconds() < 0: print( Дедлайн уже прошел!'1) return
#	Извлекаем компоненты из timedeLta days = time_left.days
#	В seconds хранится время, не перешедшее в дни (меньше 24 часов)
Модули и пакеты 454
seconds_remainder = time_left.seconds
#	Конвертируем остаток секунд в часы, минуты и секунды hours = secondsremainder // 3600 minutes = (seconds_remainder % 3600) // 60 seconds = seconds_remainder % 60
print(f"Осталось {days} дней, {hours} часов, {minutes} минут и {seconds} секунд’)
deadline = datetime(2026, 3, 15, 18, 0, и) # is мирта 2026 гада, 18:00:00 is_deadline(deadline)
Вывод:
Осталось: 105 дней, 16 часов, 48 минут и 0 секунд
Пример 2. Расчёт возраста человека
Функция get_age(birth_date) рассчитывает возраст человека, рождённого в дату birth date, как количество прожитых лет, дней и секунд:
from datetime import date
def get_age(oirth_date: date) -> None:
...Выводит на экран информацию о возрасте человека и количестве его прожитого времени.
Параметры:
birth_date: Дата рождения человека.
II II II
print(f"fleHb рождения: {birth_date}") print(f"TeKyinafl дата: {date.today()} )
#	Вычисляем разницу в виде timedeLta duration = date.today() - birth_date total_days = duration.days
#	Приближенный расчет возраста
approx_years = int(total_days // 365.25) # Учет високосных годов remaining_days = int(total_days % 365.25)
#	Общее количество секунд
total_seconds = int(duration.total_seconds())
print(f‘Примерный возраст: {approx_years} лет и {remaining_days} дней’) print(f’Общее количество прожитых дней: {total_days} )
print(f’Общее количество прожитых секунд: {total_seconds}’)
birth_date = date(1995, 8, 20) # 20 августа 1995 года
Модули и пакеты 455
get_age(birth_date)
Вывод:
День рождения: 1995-08-20
Текущая дата: 2025-12-28
Примерный возраст: 30 лет и 130 дней
Общее количество прожитых дней: 11088
Общее количество прожитых секунд: 958003200
Пример 3. Генерация списка дат с заданным интервалом
Функция get_date_series(start_date, interval, series_length) генерирует список из series_length дат, начиная с даты start_date, с интервалом interval между каждой датой:
from ciatetime import date, timedelta
def get_date_series( startdate: date, interval: timedelta, series_length: int ) -> list:
..Генерирует список дат с заданным интервалом.
Параметры:
start_date. Начальная дата.
interval: Интераал между датами.
series_length: Количество дат в списке.
Возвращает: Список из series_length дат. tt tt II
#	Цикл для генерации 10 дат date_series = [1 current_date = start_date for i in range(series_length): date_series.append(current_date) # Добавление временного интервала к дате current_date +» interval
return date_series
startdate = aate(2025, 11, 30)
interval = timedelta(weeks=2) # 2 недели
date_series = get_date_series(start_date, interval, 1<?) for i, d in enumerate(date_series): #	- Формат ДД.ММ.ГГГГ
print(f"Дата {1+1}: {d.strftime('%d.%m.%Y')}")
Модули и пакеты 456
Вывод:
Дата 1: 30.11.2025
Дата 2: 14.12.2025
Дата 3: 28.12.2025
Дата 4: 11.01.2026
Дата 5: 25.01.2026
Дата 6: 08.02.2026
Дата 7: 22.02.2026
Дата 8: 08.03.2026
Дата 9: 22.03.2026
Дата 10: 05.04.2026
Итоги
J Модуль datetime предоставляет классы для работы с датой и временем.
J Класс date используется для представления даты.
J Метод date.today() возвращает текущую дату (без времени).
✓	Класс time используется для представления времени.
S Класс datetime используется для представления даты и времени.
J Метод datetime. now() возвращает текущую дату и время
J Метод datetime. date() возвращает дату из объекта datetime.
S Метод datetime, time() возвращает время из объекта datetime.
S Класс timedelta представляет собой продолжительность или разницу между двумя моментами времени (объектами date или datetime).
J Метод timedelta.total_seconds() возвращает общую продолжительность промежутка в секундах.
S Вычитание из одного объекта даты и времени другого вычисляет разницу между ними, то есть временной промежуток timedelta.
✓	Промежуток времени timedelta может быть прибавлен или вычтен из объектов date и datetime.
Дату и время можно сравнивать с помощью стандартных операторов сравнения (от большего компонента к меньшему).
J Метод obj. strftime() преобразует объект date, time или datetime в читаемую строку в соответствии с заданным шаблоном.
J Метод datetime. strptime() преобразует строку с датой и временем в объект datetime.
Задания для самопроверки
1.	Можно ли из существующего объекта datetime получить компонент
Модули и пакеты 457
даты (объект date) и компонент времени (объект time)?
2.	Создайте две даты: дату начала проекта st a rt_d ate = date(2025, 1, 15) и дату его завершения end_date = date(2025, 5, 20) Вычислите длительность проекта выведите его на экран.
3.	Дана строка с датой и временем date str = “23 January 2026, 14:30”. Преобразуйте эту строку в объект datetime и выведите её на экран.
4.	Получите текущую дату и время и преобразуйте этот объект в строку в следующем формате: "Сегодня: %А, %d.%m.%Y. время: %H:%M:%S". Выведите на экран отформатированную строку.
5.	Напишите функцию is_event_passed(event_dt), которая принимает объект datetime (дату события event_dt) и сравнивает его с текущим моментом (datetime.now()). Функция должна вернуть True, если событие уже прошло, и False, если оно произойдёт в будущем. Вызовите эту функцию для двух произвольных дат и выведите результаты на экран.
6.	5 УСТАНОВКА СТОРОННИХ ПАКЕТОВ. ВИРТУАЛЬНОЕ ОКРУЖЕНИЕ
Хотя стандартная библиотека Python содержит согни модулей и является отличной базой, она не решает абсолютно все возможные задачи. Программисту могут требоваться более специализированные инструменты, например, для создания веб-приложений, работы с графикой или машинного обучения.
Именно здесь на помощь приходят сторонние пакеты или библиотеки, созданные сообществом Python.
Виртуальное окружение
Однако сторонние библиотеки могут развиваться и функции, которые были в одной версии, могут отсутствовать или работать абсолютно иначе в другой.
Например, в одном проекте вы работаете с данными с помощью библиотеки pandas версии 2.0.1, а ваш новый проект требует более современной версии 2.3.3. Если вы обновите pandas глобально на компьютере, то можете сломать ваш первый проект. Поэтому каждый проект должен иметь своё виртуальное окружение - изолированную папку, которая содержит собственную копию
Модули и пакеты 458
интерпретатора Python и уникальный набор установленных библиотек. Тогда инструменты. установленные для одного проекта, будут невидимы для другого, и вы всегда можете точно определить, какие именно библиотеки и каких версий нужны для запуска проекта.
Создание виртуальною окружения
Управление виртуальным окружением осуществляется через терминал, и вы можете использовать как встроенный терминал VS Code, так и терминал операционной системы или командную строку, однако убедитесь, что вы находитесь в папке проекта.
Для создания виртуального окружения используется встроенный модуль venv. который входит в стандартную библиотеку Python. Его необходимо запустить через терминал с помощью команды python -m venv имяокружения.
Флаг -ш (от англ, module - модуль) в этой команде означает запуск модуля, а имя окружения - это название папки, в которой будет храниться окружение. Чаще всего эту папку называют venv или .venv (от англ, virtual environment виртуальное окружение).
Также не забывайте, что если вы используете Linux или macOS, то вместо python следует писать python3. то есть явно указывать версию языка.
Теперь давайте создадим виртуальное окружение, которое назовём .venv:
python -m venv .venv
После выполнения этой команды в текущей папке появится новая папка .venv со всеми необходимыми файлами.
Активация виртуального окружения
Для работы в виртуальном окружении его нужно активировать. Команда активации зависит от операционной системы.
На Windows нужно запустить пакетный файл:
.venv\Scripts\activate
А на Linux или macOS запустить bash-скрипт:
source .venv/bin/activate
Если вы создали виртуальное окружение с другим именем, то укажите его вместо .venv.
После активации в терминале появится префикс с именем виртуального
окружения:
Модули и пакеты 459
(.venv) PS C:\Projects\Chapter6>
Этот префикс означает, что теперь все команды, связанные с пакетами, будут выполняться только в этом виртуальном окружении (папке) и не затронут глобальные настройки.
Деактивации виртуально! о окружения
Если вы хотите вернуться к глобальным настройкам системы, то можете деактивировать окружение:
deactivate
Префикс (. venv) исчезнет, и вы снова будете использовать глобальный интерпретатор Python.
Управление сторонними библиотеками
Самым большим хранилищем пакетов Python является РуР! (от англ. Python Package Index - Каталог пакетов Python). Он содержит сотни тысяч пакетов, которые были загружены в него разработчиками со всего мира.
Для установки пакетов используется менеджер пакетов pip - программа для установки, обновления и удаления пакетов Python. Название pip является рекурсивным акронимом РТР Installs Packages (с англ. - Установщик пакетов РТР), то есть содержит само себя.
Начиная с версии Python 3.4, pip автоматически устанавливается вместе с Python. Вы можете убедиться, что он доступен, введя в терминале команду:
pip --version
Вы должны увидеть ответ, похожий на этот:
pip 25.1.1 from С:\Users\Irina\AppData\Local\Programs\Python\Py-thon313\Lib\site-packages\pip (python 3.13)
Если же система выдает ошибку, вам, возможно, потребуется переустановить Python.
Установка пакетов
Для установки пакета используется следующая команда pip install имя_пакета. Если вы не хотите устанавливать этот пакет глобально, то перед установкой убедитесь, что ваше виртуальное окружение активно и вы видите его имя в скобках перед командной строкой.
Модули и пакеты 460
Давайте установим пакет rich, предназначенный для красивого вывода данных в терминале. Он позволяет добавлял и цвета, стили и даже создавать таблицы:
pip install rich
После этого pip скачает нужные файлы пакета и все его зависимости в папку site-packages вашего активированного виртуального окружения.
Теперь библиотека rich доступна для импорта в любом модуле текущего проекта. Например, мы можем импортировать из неё функцию print(), которая заменил вслроенную функцию print():
from rich import print
print( [bold уе11о\«]Привет, мир! [/bold yellow]")
Благодаря тегам bold и yellow выведенный текст в терминале будет выделен жирным шрифтом и жёлтого цвета:
Привет, мир!
Встроенная функция print (). в свою очередь, такой вывод не поддерживает и вывела бы всю строку целиком, включая теги.
Установка определённой версии пакета
По умолчанию pip устанавливает последнюю версию пакета, но это поведение можно настроить, указав желаемую версию после двух знаков равно (==).
Например, нам нужна библиотека numpy версии 2.2.1:
pip install numpy==2.2.1
Эта команда установит именно указанную версию библиотеки numpy.
Данные об установленном пакете
Команда pip show имя_пакета выводит в терминал подробную информацию об установленном пакете. Например, выведем данные о ранее установленном пакете numpy:
pip show numpy
В результате мы увидим полное описание пакета numpy:
Name: numpy
Version: 2.2.1
Summary: Fundamental package for array computing in Python
Home-page:
Author: Travis E. Oliphant et al.
Author-email:
Модули и пакеты 461
License: Copyright (c) 2005-2024, NumPy Developers. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Location: C:\Projects\Chapter6\.venv\Lib\site-packages
Requires:
Required-by:
Пункт Requires обычно содержит зависимости, то есть пакеты, необходимые для работы, a Required-by - пакеты, для которых данный пакет является зависимостью.
Список всех установленных пакетов
Команда pip list выводит в терминал список всех установленных паке
тов:
pip list
В результате мы увидим все установленные пакеты и их версии, включая
зависимости:
Package	Version
markdown-it-py	4.0.0
mdurl	0.1.2
numpy	2.2.1
Pip	24.0
Pygments	2.19.2
rich	14.2.0
Установка пакетов из файла
Пакеты можно устанавливать не только вручную, но из файла, который часто называют requirements.txt. В этом файле должны быть построчно перечислены все необходимые пакеты и их точные версии.
Для того, чтобы создать такой файл используется команда pip freeze:
pip freeze
По умолчанию она просто выводит в терминал список всех установленных
пакетов их версии:
markdown - it-ру==4.0.0 mdurl==0.1.2
numpy==2.2.1 pip==24.0 Pygments==2.19.2 rich==14.2.0
Модули и пакеты 462
Но мы можем перенаправить вывод с помощью символа > и записать вывод в файл requirements.txt:
pip freeze > requirements.txt
Тогда другой разработчик или вы сами можете воспроизвести точно такое же виртуальное окружение одной командой:
pip install -г requirements.txt
Здесь флаг - г (от англ, requirements - требования) указывает pip прочитать и установить все пакеты, указанные в файле.
Удаление пакета
Если пакет вам больше не нужен, или вы забыли активировать виртуальное окружение и случайно установили пакет глобально, то команда pip uninstall имя_пакета позволит полностью удалить пакет и его зависимости.
Допустим, мы решили не работать со сложными вычислениями, поэтому удалим модуль numpy
pip uninstall numpy
После этого pip выведет в терминал список папок, которые будут удалены и попросит подтвердить удаление:
Found existing installation: numpy 2.2.1
Uninstalling numpy-2.2.1:
Would remove:
c:\projects\chapter\.venv\lib\site-packages\numpy-2.2.l-cp313-cp313-win_amd64.whl
c:\projects\chapter\.venv\lib\site-packages\numpy-2.2.l.dist-info\*
c:\projects\chapter\.venv\lib\site-packages\numpy.libs\lib-scipy_openblas64_-43ellff0749b8cbe0d61Sc9cf6737e0e.dll
c:\projects\chapter\.venv\lib\site-packages\numpy.libs\msvcpl40-
d64049c6e3865410a7dda6a7e9f0c575.dll
c:\projects\chapter\.venv\lib\site-packages\numpy\*
c:\projects\chapter\.venv\scripts\f2py.exe
c:\projects\chapter\.venv\scripts\numpy-config.exe
Proceed (Y/n)?
После подтверждения (ввода буквы Y и нажатия на клавишу Enter), библиотека numpy будет полностью удалена из текущей среды.
Примеры
Пример 1. Библиотека geocode и получение полного адреса
Функция geocode_address(address) принимает строку address с адресом обьекта и выводит на экран его полный адрес, включая ширину и долготу. Для Модули и пакеты 463
поиска адреса используется пакет geopy. позволяющий использовать бесплатный сервис для геокодирования Nominatim. Он позволяет получать географические координаты (широту и долготу) по адресу, находить адрес по координатам, а также вычислять расстояние между двумя точками.
Пакет geopy является сторонним пакетом, который нужно установить с помощью pip:
pip install geopy
При создании экземпляра класса геокодера Nominatim ему обязательно передаётся параметр user_agent, который требуется для идентификации приложения при обращении к сервису. Метод geocode() созданного объекта отправляет строку с адресом и пытается найти соответствующее местоположение. Если местоположение найдено, то возвращается объект Location с атрибутами address, latitude и longitude для полною адреса, широты и долготы соответственно, иначе возвращается None:
•from gecpy.geocoders import Nominatim
def geocode address(address: str) -> None:
'‘""Выводит на экран полный адрес с географическими координатами (широтой и долготой).
Параметры:
address: Строка, содержащая адрес для геокодирования (например, "Красная Площадь, Москва"), fl II II
#	Используем Nominatim (OpenStreetMap) geolocato^ = Nominatim(user_agent="my_python_app )
#	Геокодирование location = geolocator.geocode(address) if location:
print(f'Полный адрес: {location.address} )
print(f"Широта: {location.latitude-.4f}")
print(f"Долгота: {location.longitude:.4f}’) else:
print( Адрес не найден")
geocode_address( Красная Площадь, Москва")
Вывод:
Полный адрес: Красная площадь, 4, Китай-город, Тверской район, Москва, Центральный федеральный округ, 109012, Россия
_______Широта: 55.7536_______________________________________________________________
Модули и пакеты 464
Долгота: 37.6215
Пример 2. Использование библиотеки pandas в IPYNB-файле
Библиотека pandas является практически стандартом для работы с данными, включая их обработку, фильтрацию, группировку и подготовку к визуализации или машинному обучению. Её ключевыми структурами данных являются Series - одномерный массив с метками и DataFrame - двумерная структура данных, похожая на таблицу.
Однако вместо классических модулей с расширением . ру для анализа данных и машинного обучения используются файлы с расширением . ipynb (от англ. Interactive Python NoteBook - интерактивный блокнот Python). Они сочетают в себе код (Python) и текст (объяснения, заголовки, графики) в одном окне, при этом код можно выполнять не сразу весь, а запускать отдельные ячейки.
Давайте создадим файл с расширением .ipynb в VS Code. Блокнот создаётся пустым, поэтому нужно создать ячейку для кода с помощью кнопки + Code ф. При этом IPYNB-файлы позволяют использовать команды pip прямо в ячейках. начиная их с символа % (2), поэтому библиотеку pandas можно установить прямо в блокноте через команду % pip install pandas. Установка выполняется в ту виртуальную среду, которая выбрана для данного блокнота.
Запуск кода в выбранной ячейке осуществляется через нажатие клавиш Shift + Enter, однако при первом запуске потребуется выбрать виртуальную среду ®.
Рисунок 24. Запуск первой ячейки с кодом
После этого нужно разрешить установить Python некоторые зависимости и библиотека будет установлена.
Модули и пакеты 465
Рисунок 25. Установка дополнительных зависимостей для IPYNB-файла
Теперь создадим ещё две ячейки с кодом. В первой импортируем библиотеку pandas через псевдоним pd:
import pandas as pd
А во второй ячейке создадим словарь data с товарами в интернет-магазине, который передадим классу DataFrame для создания полноценной таблицы с данными. Для того, чтобы вывести первые 5 строк такой таблицы используется метод d+.head().
data = {
"ID": [101, 102, 103, 104, 105, 10b, 107],
"Название": [
"Ноутбук Acer",
"Мышь Logitech",
"Хлеб Бородинский",
"Сыр Гауда",
"Монитор Dell",
"Учебник по математике",
"Кофеьарка"
L
"Цена": [75е00, 1200, 55, 450, 25000, 980, 4500],
"Количество": [10, 50, 150, 30, 15, 40, 12],
"Категория": [
"Электроника",
"Электроника' ,
"Продукты",
"Продукты",
"Электроника",
"Книги",
"Электроника"
]
Модули и пакеты 466
}
#	Создадим DataFrame из словаря
df = pd.DataFrame(data)
#	отобразим красивую таблицу
df.head()
Результат выполнения кода в каждой ячейке отображается под этой ячей-
кой.
	0.0s		Цена	Количество	Категория
	ID	Название			
0	101	Ноутбук. Acer	75000	10	Электроника
1	102	Мышь Logitech	1200	50	Электроника
2	103	Хлеб Бородинский	55	150	Продукты
3	104	Сыр Гауда	450	30	Продукты
4	105	Монитор Dell	25000	15	Электроника
Рисунок 26. Вывод DataFrame в IPYNB-файле
Если у вас много ячеек с кодом, то необязательно запускать каждую из них вручную через сочетание клавиш Shift + Enter, а вы можете нажать на кнопку Run АП в верхней панели, которая выполнит код во всех ячейках, начиная с первой.
ИТОН!
S Каждый проект должен иметь своё виртуальное окружение - изолированную папку, которая содержит собственную копию интерпретатора Python и собственный, уникальный набор установленных библиотек.
J Команда python -m venv имя_окружения создаёт виртуальное окружение с помощью встроенного модуля venv.
S Для работы в виртуальном окружении его нужно активировать. Команда активации зависит от операционной системы.
J Самым большим хранилищем пакетов Python является РуР1, содержащий сотни тысяч пакетов, которые были загружены в него разработчиками со всеми мира.
J Для установки пакетов используется менеджер пакетов pip - программа
Модули и пакеты 467
для установки, обновления и удаления пакетов Python.
J Команда pip install имя пакета устанавливает пакет в текущую активированную виртуальную среду.
J Список пакетов проекта и их версии обычно сохраняют в файл require-ments .txt с помощью команды pip freeze > requirernents.txt.
J Команда pip install -г requirements .txt устанавливает все пакеты из файла requirernents.txt.
Задания для самопроверки
1.	Почему важно использовать виртуальное окружение для каждого проекта, а не устанавливать библиотеки глобально?
2.	Для чего используется команда pip freeze? Как сохранить результат этой команды в файл?
3.	Объясните роль файла requirernents.txt в совместной работе над проектом или при переносе проекта на другой компьютер. Какая команда используется для установки всех библиотек из этого файла?
4.	Используя модуль venv, создайте виртуальное окружение и назовите его .venv. Затем активируйте его. Все следующие задания выполняйте в этом виртуальном окружении.
5.	В активированном окружении используйте pip для установки популярной библиотеки для анализа данных pandas (последней версии).
6.	Используйте команду pip, чтобы получить краткую информацию о только что установленном пакете pandas.
7.	Удалите текущую версию pandas из активированного виртуального окружения.
Модули и пакеты 468
В enor_logs_2025L ®
В foc«i$t_mode £
В projr^ ur
В я!«_г*РО
В u®er_pretv
«1 websrte_«nalytic$
Глава 7
Работа с файлами
7.1 ЧТЕНИЕ И ЗАПИСЬ ФАЙЛОВ
В большинстве реальных приложений недостаточно просто обрабатывать данные в оперативной памяти, так как часто требуется сохранять информацию между запусками программы или за!ружагь ее из внешних источников. Для этого используется работа с файлами надиске.
По способу хранения информации все файлы делятся на текстовые и бинарные (двоичные).
Байты внутри текстового файла представляют символы (с учётом используемой кодировки), которые мы можем прочитать и увидеть на экране. Такие файлы удобны для хранения текстовой информации.
В бинарных файлах байты не соответствуют символам напрямую, а представляют собой различные структуры данных: машинные инструкции, пиксели изображений, аудиоданные и другие элементы, в зависимости от формата файла. Поэтому бинарные файлы не предназначены для непосредственного чтения человеком.
К текстовым файлам относятся, например, обычные текстовые файлы с расширением .txt. тогда как бинарные файлы могут представлять собой изображения. аудиофайлы или исполняемые файлы программ.
Понимание различий между текстовыми и бинарными файлами важно при работе с файлами в Python, поскольку от тина файла зависит способ его открытия.
Открытие файлов
Прежде чем начать читать или записывать данные, файл необходимо открыть. Функция ореп() выполняет эту задачу и возвращает файловый объект. Он используется для всех последующих операций чтения и записи, а также для закрытия файла после завершения работы с ним.
Работа с файлами 469
Функция	open(filej mode="r", encoding=None)
Описание	Открывает файл file для чтения или записи • file - абсолютный или относительный путь к файлу
Параметры
Необязательные параметры:
•	mode - режим открытия файла, по умолчанию mode="r"
•	encoding- кодировка для перевода байтов в символы. По умолчанию не задана
Возвращаемое значение
Файловый объект
По умолчанию файл открывается в режиме чтения (“г"), то есть данные из файла можно прочитать, но нельзя добавить новые. Однако существуют режимы записи и дозаписи, которые можно совмещать с чтением с помощью символа +.
Таблица 39 - Режимы открытия текстового файла
Ре-	Сокраще-		Дей-	Начальная	Файл не суще-	Файл очи-
жим	ние or		ствия	пощция указа геля	ствует	щается при открытии
11 pH ,,г+"	read англ. -тать)	(с чи-	Чтение Чтение и переза- пись	Начало файла	Вызывается исключение File-NotFoundError	Нет
"w" "w+"	write англ. -писать)	(с за-	Запись Запись и чтение	Начало файла	Файл создаётся	Да
"а" "а+"	append англ. -бавить)	(с ДО-	Дозапись Дозапись и чтение	Конец файла	Файл создаётся	Нет
Режимы открытия файлов в Python делятся на текстовые и двоичные. При чтении текстовых файлов все методы возвращают строки, а двоичных - байты. Для работы с бинарными файлами (изображения, исполняемые файлы) к текстовому режиму добавляется буква "Ь" (от англ, binary’ - бинарный), например, "rb" или "wb".
Работа с файлами 470
Далее мы будем рассматривать работу только с текстовыми файлами, так как они наиболее часто используются при обработке текстовой информации и позволяют сосредоточиться па базовых приёмах файлового ввода и вывода.
Также при работе с файлами важно понимать, где находится указатель. Он определяет позицию, с которой будет начата следующая операция чтения или записи. При открытии файла он находится в начале или конце, а при чтении или записи он автоматически сдвигается.
Давайте создадим текстовый файл data.txt с произвольным текстом и скрипт main.py, в котором откроем созданный файл:
data_file = open( 'data.txt’*., encoding="utf-8r')
Путь к файлу может быть абсолютным и начинаться с буквы диска, например, ,,C:\projects\tasks\data.txt", или относительным и указываться относительно запущенного файла. Поэтому если файл находится в той же папке, что и программа, достаточно указать только его имя
Кроме того, так как в большинстве случаев мы работаем с кириллицей, рекомендуется указывать кодировку "utf-8".
Если запустить написанный код, то ничего не произойдёт, так как файл существует, и программа завершится корректно. По если удатить файл data.txt и снова запустить программу, то будет вызвано исключение FileNotFoundError.
В зависимости от режима открытия файла изменяется требование к его существованию. Так, режимы записи (V), дозаписи С'а") и комбинированные режимы ("r+", "w+", "а+") создадут новый файл при его отсутствии:
data_+ile = ореп( 'new_data.txt", mode="w , encoding="utf8")
После выполнения этого кода в папке со скриптом у вас должен создаться новый текстовый файл new_data.txt.
Закрытие файла и контекстный менеджер
В конце работы с файлом его необходимо закрыть с помощью метода file.close(). Это нужно для освобождения системных ресурсов, обеспечения целостности файла и избегания потери данных. Также только после закрытия файла гарантируется, что все данные из оперативной памяти были успешно записаны в файл.
Работа с файлами 471
Метод
file.close()
Описание
Закрывает открытый файл file
Возвращаемое значение
None
Из-за критической важности закрытия файлов в Python существует более безопасный и предпочтительный способ работы с ними - использование контекстного менеджера с ключевым словом with
with иреп(путь_к_файлу, режим, кодировка) as имя_сбъекта_с_файлом:
# Блок кода для работы с файлом
# Здесь файл автоматически закрывается
Он гарантирует, что метод file.close() будет вызван автоматически, независимо от того, как завершился блок кода with - успешно или с исключением. Это стандартный и рекомендуемый способ работы с файлами в Python.
Чтение данных из файла
После того как файл успешно открыт в любом из режимов чтения, объект файла предоставляет несколько методов для извлечения данных:
о file. read () - считывает все содержимое файла в одну строку;
о file.readline() - считывает одну строку из файла;
о file.readlines() - считывает все строки файла в список.
Выбор метода зависит от размера файла и от того, как требуется обрабатывать ею содержимое.
При этом строки как тип str считываются только в текстовых файлах. Если открыт бинарный файл, то все манипуляции производятся над неизменяемыми последовательностями байтов, которые в Python представлены типом bytes. Однако далее мы будем рассматривать работу только с текстовыми файлами.
Для начала давайте подготовим данные для чтения и создадим файл cake_recipe.txt с классическим рецептом бисквита:
Яйцо куриное - 4 шт.
Мука пшеничная - 120 г
Сахар - 110 г
Ванильный сахар - 10 г
Работа с файлами 472
Чтение все! о файла в строку
Метод file.read() является самым прямым способом, так как он читает всё содержимое файла целиком и возвращает его в виде одной строки.
Функция
Описание
Параметры
Во шрашаемое значение
file.read(size=-l)
Читает файл file и возвращает его содержимое в виде одной строки
Необязательные параметры:
• size - количество байтов, которое нужно прочитать. По умолчанию считывается весь файл
Строка
Для текстовых файлов метод file.read() возвращает одну большую строку, содержащую все символы файла, включая символы перевода строки (\п): with ореп( cake_recipe.txt"., “г", encoding="utf-8 ) as cake_recipe_file: print(cake_recipe_file,read()) # Вывод: Яйцо куриное -	4	шт.
#	Мука пшеничная	-	120	г
#	Сахар - 110 г
#	Ванильный сахар	-10г
При открытии файла в режиме записи ("w") или записи и чтения ("w+") содержимое файла удаляется без возможности восстановления. Поэтому будьте внимательны при выборе режима открытия файла.
Этот метод не рекомендуется использовать для работы с очень большими файлами (гигабайты данных), так как он мгновенно загружает весь файл в оперативную память, что может привести к ее переполнению и вызову исключения МетогуЕггог
Однако параметр size позволяет указать максимальное количество символов для чтения за один раз:
Работа с файлами 473
Читает файл file и возвращает одну строку (до символа \п включительно)
with open( cake_recipe.txt", "г , encoding="utf-8') as cake_recipe_file: print(cake_recipe_file.read(a)) # Вывод: Яйцо
Здесь из целого файла были получены только первые 4 символа.
Построчное чтение
Вместо того, чтобы сразу читать весь файл целиком, мы можем читать его построчно с помощью метода file.readline().
Фу нкция	file, readline (si ze=-l)
Описание
Необязательные параметры:
Параметры • size - количество байтов, которое нужно прочитать. По умолчанию считывается строка до символа \п
Во твращаемое значение
Этот метод читает символы от текущей позиции до ближайшего символа перевода строки (\п), включая сам этот символ, и возвращает их в виде строки: with ореп( cake_recipe.txt", "г , encoding="utf-8") as cake_recipe__file: print(cake_reciDe_flie.read]ine(), end=" ) # Вывод: Яйцо куриное - 4 шт.
Здесь мы присваиваем параметру end функции print() значение в виде пустой строки для того, чтобы избежать дополнительной пустой строки при выводе, так как прочитанная строка уже заканчивается символом \п.
При каждом последующем вызове метода указатель перемещается на начало следующей строки:
with ореп( cake_recipe.txt", "г", encoding=“utf-К ") as cake_reclpe_file: print(cake_recipe_fi]e.readline(), end=”") # Вывод: Яйцо куриное - 4 шт.
print(cake_recipe_file.readline(), end= ) # Вывод: Мука пшеничная - 120 г print(cake_recipe_fi]e.readline(), end=" ) # Вывод: Сахар - 110 г
Как и в методе file.read(), размер читаемой строки можно ограничить, просто передав методу целое число, указывающее количество байт, которые
Работа с файлами 474
нужно прочитать:
with open( cake_recipe.txt"., "г", encoding="utf-8 ) as cake_recipe_file. print(cake_recipe_file.readline(4), end= )
#	Вывод: Яйцо
Наиболее распространённым способом построчного чтения файла является перебор его в цикле for:
with npen('cake_recipe.txt' j "r", encoding="utf-8”) as cake_recipe_file: for line in cake_recipe_file:
Printline, end=" )
#	Вывод: Яйцо куриное - 4 шт.
#	Вывод: Мука пшеничная - 120 г
it Вывод: Сахар - 110 г
#	Вывод: Ванильный сахар - 10 г
При каждой итерации цикл возвращает следующую строку, не загружая весь файл сразу в память.
Чтение всего файла в список строк
Метод file, readlines() сочетает удобство чтения всего файла с ориентацией на строки. Он читает все строки из файла до конца и возвращает их в виде
списка строк.
Функция
Описание
Возвращаемое значение
file.readlines()
Читает файл file и возвращает список С1рок
Список строк
Каждый элемент списка будет содержать полный текст строки, включая символ \п:
with ореп( cake_recipe.txt", "г", encoding="utf-8") as cake_recipefile: print(cake_recipe_file.read]ines())
#	Вывод: ['Яйцо куриное - 4 шт.\п\ 'Мука пшеничная - 120 г\п', 'Сахар -110 г\п'} 'Ванильный сахар -10г']
Хотя результат удобно структурирован, этот метод, как и file.read(), загружает весь файл в память, поэтому он тоже не подходит для очень больших файлов.
Работа с файлами 475
Запись данных в файл
Процесс сохранения информации на диск называется записью. Для этого файл должен быть открыт в одном из режимов, разрешающих запись После открытия файла объект файла предоставляет два основных метода для записи:
о file.write() - добавляет одну строку в файл;
о file, writ elines () - добавляет в файл все строки из списка.
Запись одной строки
Метод file.write() является базовым инструментом для записи данных в файл. Он принимает в качестве аргумента одну строку и записывает ее в файл, начиная с текущей позиции указателя
Функции	file, write(line)
Описание	Записывает строку line в файл file
Параметры
Возвращаемое значение
• line - строка, записываемая в файл file
Количество записанных символов
В зависимости от режима открытия файла меняется позиция указателя как начата операций чтения и записи:
о начало файла (позиция 0) - "r+", "w" и "w+"
о конец файла - "а" и "а+"
Запись одной строки в режиме г+ и управление указателем
Режим "г+" позволяет выполнять как чтение, так и запись в одном сеансе работы с файлом, при этом файл должен быть уже создан, иначе вызывается исключение FileNotFoundError.
Например, давайте откроем предыдущий файл с рецептом бисквита и запишем в него строку "Нода - 250 мл":
with <>реп( cake_recipe.txt", ”г+ , enroding="ut-f-8") as cake_recipe_file:
cake_recipe_file.write( 'Вода - 250 мл ) print(cake_recipe_file.readlines())
# Вывод: [‘oe - 4 шт. \n'u 'Мука пшеничная - 120 г\п'_, 'Сахар - 110 г\п', 'Ванильный сахар - 10 г ']
Работа с файлами 476
Здесь новая строка перезаписывает содержимое файла, поэтому всё ещё остаётся часть старой строки "Яйцо куриное - 4 шт.\п", а любые операции чтения или записи проводятся с новой позиции указателя. Это значение позволяет получить метод file.tell().
Функция
Описание
Возвращаемое значение
file.tell()
Возвращает текущую позицию указателя в файле file
Текущая позиция указателя в байтах
Результат этого метода не всегда совпадает с количеством символов до указателя, так как в стандартной кодировке LTF-8 большинство кириллических символов занимают 2 байта.
with ореп( cake_recipe.txt"., "г+‘ , encuding="utf 8”) as cake_recipe_file: cake_recipe_file.write(‘Вода - 250 мл') print(cake_recipe_file.tell()) # Вывод: 19
При открытии файла указатель устанавливается в его начало (0 байт), а после записи строки "Вода - 250 мл" он перемещается на 19 байт вперёд. Однако мы можем вручную переместить указатель с помощью метода file.seek().
Функция	file.seek(offsetj whence^©)
Описание	Перемещает указатель с текущей позиции на позицию offset относительно начала файла file
Параметры	• offset - новая позиция указателя в файле file
Возвращаемое значение	Новая позиция указателя
В текстовых файлах указатель смещается на offset байтов относительно начата файла, поэтому для того, чтобы перевести его в начато файла, достаточно передать методу file.seek() число 0:
with ореп( cake_recipe.txt', "r+", encoding="utt-8") as cake_recipe_file.
cake_recipe_file.write( Вода - 250 мл' )
cake_recipe_file.seek(0)
Работа с файлами 477
print(cake_recipe_file.readlines())
#	Вывод: ['Вода - 250 млое - 4 шт.\п', 'Мука пшеничная - 120 г\п\ 'Сахар - Ив г\п\ 'Ванильный сахар - 10 г']
Это позволяет как прочитать весь файл целиком, так и более точно управлять местом для записи в файле.
Запись одной строки в режимах w и w+
Режимы "w" и "w+" используются, когда нужно создать новый файл или полностью перезаписать содержимое существующего.
Если файл, открытый в одном из этих режимов, не существует, то он будет создан. Так, если мы заранее создадим файл cream_recipe.txt для рецепта крема с произвольным текстом, то его открытие в режимах "w" и "w+" полностью удалит его содержимое:
with ореп( cream_recipe.txt', "w+", encoding= 'utf-Е ) as cream_recipe_file: cream_recipe_file.write("Caxap 240 r\n ) cream_recipe_file.write("Macnc- сливочное - 2ь0 г\п")
cream_recipe_file.seek(0) # Возвращаем указатель в начало файла
print(cream_recipe_file.readlines()) # Вывод: ['Сахар - 240 г\л', 'Масло сливочное - 260 г\п']
Здесь в файл последовательно были записаны две новые строки. Если после открытия файла указатель устанавливается в начало файла, то после записи каждой строки он перемещается в конец этой строки.
Так как метод file.write() не добавляет автоматически символ перевод строки (\п), то при необходимости он указывается вручную.
Запись одной строки в режимах а и а+
Режимы "а" и "а+" используются, когда нужно добавить новые данные в конец файла, сохраняя при этом старые данные. Если файл, открытый в одном из этих режимов, не существует, то он будет создан, однако в отличие от режимов "w" и "w+", его содержимое не будет перезаписано, а указатель устанавливается в конец файла.
Например, откроем файл cream_recipe.txt, в который мы уже записывали строки с ингредиентами, и запишем в него новые строки:
with ореп( cream_recipe.txt , "а+", encoding="utf-8') as cream_recipe_file: credm_recipe_file.write("Сметана 30% - 50и г\п )
Работа с файлами 478
cream_recipe_file.write( Вареная сгущёнка - 380 г\п‘)
cream_recipe_file.seek(0) # Возвращаем указатель б начало файла
print(cream_recipe_file.readlines())
#	Вывод: ['Сахар - 240 г\п'} 'Масло сливочное - 260 г\п'_, 'сметана 30% -Ь00 г\п'} ‘Вареная сгущёнка - 380 г\п']
Здесь все строки были добавлены в конец файла, так при открытии указатель был установлен в конец существующего содержимого.
Запись всех строк из списка
Если нужно добавить сразу несколько строк, то необязат ельно записывать их по одной, так как метод file.writelines() позволяет сразу записать последовательность (список или кортеж) строк.
Функция	file.writelines(sequence)
Описание	Записывает все строки из последовательности sequence в файл file
Параметры	• sequence - последовательность строк для записи
Возвращаемое значение	None
Этот метод может использоваться в любом режиме, разрешающим запись. Например, программно создадим новый файл shopping.txt и добавим в него список покупок:
with ореп( shopping.txt’, "w+", encoding="utf-8 ) as shopping file. cake_ingredients = ("Мука\п , "Caxap\n , 'Масло\п , "Сметана\п"} "СгущенкаХп ]
shopping_file.wrifelines(cake_ingredtents)
shopping_file.seek(0) # Возвращаем указатель в начало файла для чтения
print(shopping_file.readlinesf)) # Вывод: [‘Мука\п‘, 'Сахар\п'Л 'Масло\п', 'CMemaHa\n‘j 'Сгущёнка\п']
Здесь Python проходит по всем строкам переданной коллекции и записывает каждую из них в файл.
Также метод file.writelines(), как и метод file.write(), не добавляет символы перевода строки (\п) между элементами или после них. Если
Работа с файлами 479
необходимо, чтобы каждый элемент коллекции записывался с новой строки, следует убедиться, что символ \п уже присутствует в конце каждой строки в самой коллекции.
Примеры
Пример I. Создание резервной копии
Исходный файл data_source. txt создан следующим образом: with open("data_source.txt' , "w", encoding='utf-8") as data_file: data_file.write("BaxHbie данные для архивирования. ХпСтрока 2.\n")
Его резервная копия создаётся путём чтения его содержимого и последующей записи его в новый файл data_backup.txt:
#	Читаем содержимое исходного файла
with ореп( data_soiirce.txt", "г", encoaing="utf-t ) as data_file: file_content = data_file.read()
#	Записываем его в оезервный файл
with open( data_backup.txt", "w", encoding="utf-^ ) as backup_flie: backup_f iie. write (file_cori tent)
#	Проверяем содержимого резервного файла
with open("data_backup.txt", "r", encoding="utf-8‘) as backup_+ile: oackup_content = backup_file.read()
#	Сравниваем
is_identical = (file_content == backup_content)
print(f"Копия идентична пригиналу? {is_identical}')
Вывод:
Копия идентична оригиналу? True
Пример 2. Наличие блюд в меню
Информация о наличии блюд в ресторане хранится в списке кортежей products. Для записи этих данных в файл menu.txt они предварительно преобразуются в список строк нужного формата:
products = [
("Салат с хурмой", "В наличии ),
("Простые снеки из тыквы"> "Нет в наличии ),
("Крем-суп с баклажанами", В наличии ),
("Грузинский салат с орехами", "Снят с продажи") ]
Работа с файлами 480
#	Преобразуем список кортежей в список строк с нужным форматированием formatted_lines = [f"{name}: {status}\n" for name, status in products]
#	Записываем строки в файл
with open('menu.txt", "w", encoding= utf-1 ) as menufile:
# записываем все строки из списка за один вызов menu_file.writelines(formatted_lines)
#	Читаем полученный файл
with open(‘menu.txt", "г", encoding= utf-t ) as menu_file: print(menu_file.read())
Вывод:
Салат с хурмой: В наличии
Простые снеки из тыквы: Нет в наличии
Крем-суп с баклажанами: н наличии
Грузинский салат с орехами: Снят с продажи
Пример 3. Тестирование чтения большою файла
В целях тестирования иро1рамма моделирует чтение большою файла из 10000 строк. Вместо загрузки всего содержимого файла в память, программа обрабатывает его строки по одной в цикле for:
#	Создаем пример большого Файла (10000 строк)
with ореп( large_data.txt'’, "w", encoding= utf-8 ) as data_file:
for i in range(10000):
data_file.write(f“Строка данных №{i+l}\n )
linecount = 0
total_char_count = 0
with open( large_data.txt' , "r", encoding="utf- ) as data_file: for line in data_file:
line_count += 1
total_char_count += len(line)
# Выводим прогресс для демонстрации if line_count % 2000 == 0:
print(f"Обработано {line_count} строк...“)
print(f'Bcero строк в файле: ;line_ccunt}")
print(f Общее количество символов: {total_char_count} )
Вывод:
Обработано 2000 строк...
Обработано 4000 строк...
Обработано 6000 строк...
Обработано 8000 строк...
Обработано 10000 строк...
Всего строк в файле: 1000»
Работа с файлами 481
Общее количество символов: 508894
Итог и
По способу хранения информации все файлы деля гея на текстовые и бинарные (двоичные).
J Функция open() открывает файл и возвращает файловый объект.
J По умолчанию файл открывается в режиме чтения ("г"), но также существуют режимы для записи ("w") и дозаписи ("а"), которые можно совмещать с чтением с помощью символа +.
Режимы "w" и "w+" используются, когда нужно создать новый файл или полностью перезаписать содержимое существующего.
J Режимы "а" и "а+" используются, когда нужно добавить новые данные в конец файла, сохраняя при этом старые данные.
J Для работы с бинарными файлами к режиму добавляется буква "Ь", например. "rb" или "wb".
J В конце работы с файлом его необходимо закрыть с помощью метода file.close(). Но более предпочтительным методом является использование контекстного менеджера with open(...) as имя_объекта_с_файлом. который гарантирует вызов этого метода автоматически вне зависимости от того, как завершился блок кода with.
S Метод file.read() считывает все строки файла в одну строку, а метод file.readlines() - в список.
✓	Метод file.readline() считывает одну строку из файла. Наиболее распространённым способом построчного чтения файла является перебор его в цикле for
J Метод file.write() записывает строку в начало (режимы "r+", "w" и "w+") или конец (режимы "а" и "а+") файла.
✓	Метод file.writelines() записывает в файл последовательность (список или кортеж) с)рок.
Задания для самопроверки
1.	Объясните основное различие между текстовыми и бинарными (двоичными) файлами с точки зрения того, что представляют собой байты внутри них.
2.	В чём главное отличие режима "а" от режима "w" при работе с
Работа с файлами 482
существующим файлом?
3.	Создайте файл notes.txt. открыв его в режиме записи с кодировкой "Ulfs'', и запишите в него строки "Первая заметка\п" и "Вторая заметка\п".
4.	Откройте файл notes.txt из предыдущего задания в режиме чтения и выведите на экран список строк этою файла.
5.	Программно создайте файл prices.txt. где каждая строка содержит одно число:
15.5
22.3
10.0
Откройте этот файл в режиме чтения, прочитайте и преобразуйте каждую строку в число с плавающей точкой (float) и вычислите среднее арифметическое всех цен, которое выведите на экран.
7.2 РАБОТА С CSV-Ф АЙЛ AM И
В реальной жизни данные редко хранятся в виде сплошного текста. Чаще всего они представляют собой структу рированные данные, например, таблицы или словари, поэтому для их эффективного хранения требуется не просто текстовый формат .txt, а более специализированные форматы, устанавливающие строгие правила для организации данных. Например, для хранения табличных данных часто используются CSV-файлы (от англ. Comma-Separated Values- значения, разделённые запятыми).
Стандартная библиотека Python предоставляет набор модулей, позволяющий работать с файлами различных форматов, в том числе, с CSV-файлами. Для этого предназначен модуль csv, который берёт на себя всю работу по кодированию, декодированию и обработке специальных символов, позволяя программисту сосредоточиться на работе с данными.
Особенности CSV-файлов
CSV-файл представляет собой обычный текстовый файл для представления табличных данных, структура которого подчиняется строгим правилам форматирования В таком файле каждая строка соответствует одной строке в таблице, а значения, составляющие столбцы, разделены специальным символом-разделителем (англ, - delimiter), чаще всего занятой.
Работа с файлами 483
Давайте создадим новый CSV-файл students.csv со следующим содержи
мым:
ID,ФИО,Предмет,Оценка
101,Шишкин И.И.,Живопись, 5
102,Репин И.Е.,Графика,4
103,Суриков В.И.,Пейзаж,5
104,Малевич К.С.,Архитектура,3
105,Серов В.А.,Живопись,4
106,Левитан И.И.,Пейзаж,5
Здесь первая строка содержит заголовки столонов, а каждая следующая строка - их значения.
students.csv
Ю,ФИО,Предмет,Оценка
101,Шишкин И И ,Живопись,5
102,Репин И .Е ,Графика,4
103,Сурикив В.И.,Пейзаж,5
104,Малевич К.С.,Архитектура,3
105,Серов В А.,Живопись,4
106,Левитан И И.,Пейзаж,5
Таблица «Студенты»
ID	ФИО	Предмет Оценка
101	Шишкин И И	Живопись	5
102	Репин И.Е.	Графика	4
103	Суриков В.И	Пейзаж	5
104	Малевич К.С.	Архитектура	3
105	Серов В.А.	Живопись	4
106	Левитан И.И.	Пейзаж	5
Рисунок 27. CSV-файл и соответствующая ему таблица
Такой файл можно открыть в Python, но если выполнять операции чтения и записи с помощью стандартных методов file.read() и file.write(), то нужно учитывать некоторые особенности, например:
о Если в одном из полей содержится сим вол-разделитель (запятая), то её нужно экранировать, то есть заключить внутри двойных кавычек (", ")•
о Если само поле должно содержать кавычку, ее нужно экранировать с помощью ещё одной кавычки
о На разных операционных системах по-разному обрабатывается символ перевода строки (\п или \г\п).
Работа с файлами 484
Модуль CSV
Модуль csv облегчает работу с CSV-файлами, так как он предоставляет специальные объекты чтения и записи, которые автоматически обрабатывают все правила форматирования (кавычки, разделители, переводы строки), позволяя работать с данными в CSV-файле в удобном формате списков или словарей.
Перед началом работы модуль csv следует импортировать: import csv
Объекты чтения
Объект чтения представляет собой итератор, который берёт открытый файл, разбирает его построчно согласно правилам CSV (обрабатывая кавычки, разделители и переводы строки) и возвращает данные в виде удобных структур Python (списков или словарей).
Для создания объекта чтения необходимо передать файловый объект, то есть открытый CSV-файл, функции csv.reader() или конструктору класса csv.DictReader В первом случае объект чтения позволит работать с содержимым файла как со списком, а во втором - как со словарём.
Таблица 40 - Создание объектов чтения
Харакгери-сзика Параметры Возвращаемое значение Выходной тип
Функция	Класс csv.DictReader(file)
csv.reader(file)
• file - файловый объект
Объект чтения
Список
Словарь
данных
Обрабогка зазоловков Доступ к данным
Ручная (next())
По индексу
Автоматическая (используются как ключи) По ключу
При открытии CSV-файла функции ореп() важно передать именованный аргумент newline="". Это необходимо для того, чтобы предотвратить появление лишних пустых строк из-за различий в обработке окончаний строк между
Работа с файлами 485
операционными системами. Например. Windows автоматически преобразует символ \п в два символа \г\п. но аргумент newline="" отключает встроенную обработку, позволяя модулю csv самому обрабатывать эти символы.
Чтение данных в виде списков
Давайте продолжим работу с файлом students .csv. откроем его и используем функцию csv.reader() для создания объекта чтения:
with open( students.csv"г", newline= ", encoding="utf8") as students: csv_reader = csv.reader(students)
Такой объект является итератором, поэтому его можно перебрать в цикле for. В случае использования функции csv_reader() каждая прочитанная строка представляет собой список.
Если первая строк<1 CSV-файла содержит заголовки, то их можно вручную прочитать с помощью функции next (). после чего цикл for будет перебирать все строки после заголовков:
with upen( students.csv", "г , newline= , encoding="utf-8") as students: csv_reader = csv.reader(students)
header = next(csv_reader)
print(f Заголовки: {header}')
#	Вывод: Заголовки ['ID', 'ФИО', 'Предмет', 'Оценка']
for row in csv_reader:
print(row[l], row[2], row[3], sep=' - ")
#	Вывод: Шишкин И.И. - Живопись - 5
#	Вывод: Репин И.Е. - Графика - 4
# Вывод- Суриков В.И. - Пейзаж - 5
ft Вывод: Малевич К.С. - Архитектура - 3
if Вывод: Серов В.А. - Живопись - 4
it Вывод: Левитан И.И. - Пейзаж - 5
Чтение данных в виде словарей
Однако объект чтения, полученный с помощью класса csv.DictReader. позволяет обращаться к элементам каждой строки не по индексу, а по ключам, в качестве которых выступают заголовки столбцов.
Такой способ является более предпочтительным для структурированных данных, поскольку он автоматически использует первую строку файла в качестве ключей словаря и начинает возвращать данные со второй строки:
with open( students.csv , "г", newline=" , encoding="utf-3") as students: csv_reader_dict = csv.DictReader(students)
Работа с файлами 486
for row in csv_reader_dict:
print(f"ФИО- {гои['ФИО ]) - оценка: {row['Оценка’]}")
#	Вывод: ФИО- Шишкин И.И. - оценка: 5
#	Вывод:	ФИО:	Репин И.Е. -	оценка: 4
#	Вывод:	ФИО-	Суриков В.И.	- оценка:	5
#	вывод:	ФИО:	Малевич К.С.	- оценка:	3
#	Вывод.	ФИО:	Серов В.А. -	оценка: 4
#	Вывод:	ФИО:	Левитан И.И.	- оценка:	5
Объекты записи
Объект записи используется для записи данных в CSV-файл с помощью специальных методов. Также как и объект чтения, его можно создать двумя способами: передать открытый CSV-файл функции csv.writer() для записи в него списков значений или передать его конструктору класса csv.DictWriter для записи в него словарей.
	Таблица 41 - Сравнение объектов записи
Характери-	Функция	Класс csv.Dictwriter(file,
стика	csv.writer(file)	fieldnames)
Параметры	• file - файловый объ- • file - файловый объект ект	• fieldnames - список строк с заголовками столбцов
Возвращаемое значение	Объект записи
Записываемый тип дан-	Список	Словарь
ных
Требование за-I иловков	Нет	Да
Порядок столбцов	Зависит от порядка эле- Определяется списком f ield-ментов в списке	names
Запись одной строки
Метод writer.writerow() записывает одну строку в CSV-файл. Она должна представлять собой список (для функции csv.writer()) или словарь (для класса csv.DictWriter).
Работа с файлами 487
Метод
writer.writerow(row)
Описание
Параметры
Возвращаемое значение
Записывает список пли словарь row в файл, для которого был создан объект чтения writer
• row - список или словарь, представляющий одну строку данных
None
При создании объекта записи функции csv.writer() достаточно передать файловый объект, но класс csv.DictWriter требует дополнительный параметр fieldnames с названиями столбцов CSV-файла:
with open( students.csv , "а+", newline=" , encoding="utf-8 ) as students: # Записываем одну строку как список csv_writer = csv.writer(students) student_107 = ["107 , ‘Саврасоп А.К.", "Пейзаж", 5] csv_writer. writer'Ow(student_]07)
#	Записываем одну строку как словарь columns = ["ID", "ФИО", "Предмет , "Оценка"] csv_writer_dict = csv.DictWriter(students, fieldnames=columns) students_108 = {
"ID": "108",
"ФИО": "Верещагин В.В.",
"Предмет': "Скульптура", "Оценка : 4
}
csv_writer_dict.writerow(students_108)
#	Читаем файл
students.seek(0) # Возвращаем указатель в начало файла csv_reader = csv.reader(students) for row in csv_reader:
print(row) # ...
#	Вывод: ['107', 'Саврасов А.К.', 'Пейзаж'., '5']
#	Вывод: ['108', 'Верещагин В.В.', 'Скульпгмра ', '4']
Здесь в файл students.csv были записаны две новые строки как список и как словарь.
Значения словаря записываются по ключу, в го время как элементы списка записываются в том порядке, в котором были перечислены, поэтому в списке fieldsname важен порядок названий столбцов, так как они определяют их
Работа с файлами 488
порядок в выходном CSV -файле.
Также класс csv .Dicthiriter предоставляет дополнительный метод для записи в CSV-файл списка fieldnames.
Метод	writer.writeheader()
Описание	Записывает список f ieldnames в файл, для которого был создан объект записи writer
Возвращаемое значение	None
Этот метод записывает значения из списка fieldnames в виде обычной строки от текущей позиции указателя:
with open( students.csv", "а+", newline="", encoding="utf 8") as students: columns = ["ФИО", "ID", "Предмет , Оценка'] csv_writer_dict = csv.DictWriter(students, fieldnames=columns) csv_writer_dict.writeheader()
#	Читаем файл
students.seek(B) # Возвращаем указатель в начало файла csv_reader = csv.reader(students) for row in csv_reader:
prinv(row) ff ...
#	Вывод: ['ФИО'j 'ID', 'Предмет', 'Оценка']
Запись нескольких строк
Метод writer. writerowQ позволяет записать сразу несколько, которые должны представлять собой список списков (для функции csv.writer()) или список словарей (для класса csv.DictWriter).
Функция	writer.writerows(rows)
Описание	Записывает список списков или словарей rows в файл, для которого был создан объект чтения writer
Параметры	• rows - список списков (или словарей) со строками данных
Возвращаемое значение	None
Давайте запишем несколько дополнительных строк в файл students.csv:
Работа с файлами 489
with ?pen('students.csv", "a+ , newline=" , encoding="utf-8') as students: # Записываем несколько строк как список списков csv_writer = csv.writer(students) studentsdata = [
["109", 'Врубель М.А.", Графика", 5],
["lid", "Айвазовский И.К. , "Живопись", 5] ] сsv_writer.writerows(students_data)
#	Записываем несколько строк как список словарей columns = ['ID", "ФИО", "Предмет", "Оценка ]
csv_writer_dict = csv.DictWriter(students, fieldnames=columns) extra_students_data = [
{
"ФИО”: "Поленов и.Д. ,
“ID": "111",
"Предмет : "Скульптура", "Оценка": 4
ь {
"ID": "112",
"Оценка": 4,
"ФИО": "Васнецов В.М.",
"Предмет”: "Архитектура" )
]
csvwriterdlct,writerows(extra_students_data)
#	Читаем файл
students.seek(0) # Возвращаем указатель в начало файла csvreader = csv.reader(students) for row in csv_reader:
print(row)
#	...
#	Вывод: ['109', 'Врубель м.А.', 'Графика', '5']
#	Вывод1 ['110', ’Айвазовский И.К.', 'Живопись', '5']
#	Вывод: ['111', ‘Поленов В.Д.', 'Скульптура', '4']
#	Вывод: ['112', 'Васнецов В.М.', 'Архитектура1, '4']
Несмотря на то, что в словарях ключи записаны не в гом порядке, в котором идут столбцы, они всё равно записываются в верном порядке, который был задан в списке fieldnames.
Примеры
Пример 1. Расчёт суммарного населении в каждом округе
Словарь cities_dara содержит информацию о городе, его федеральном
Работа с файлами 490
округе и населению. Эти данные сохраняются в файл city population, csv. после чего рассчитывается суммарное население каждого округа: import csv
cities_data = [
{"Город": "Москва", "Округ": "Центральный", "Население": 13000000}, {"Город": "Казань", "Округ": "Приволжский", "Население": 1250000}, {"Город": "Самара", "Округ": "Приволжский", "Население": 1150000}, {"Город": "Тверь", "Округ": "Центральный", "Население": 400000}, ]
#	Записываем словарь в CSV-файл
with open( city_population.csv", "w", newline=" , encoding="utf-8") as city_population_file:
header = list(cities_data[(l]. keys())
writer = csv.DictWriter(city_population_file, fieldnames=header)
writer.writeheader()
writer.writerows(cities_data)
#	Агрегируем данные district_population = {} with open( city_population.csv", "r", newline=" , encoding="utf-8') as city_pnpulation_file:
reader = csv.DictReader(city_population_file) for row in reader:
district = row[ Округ'] pop = int(row['Население ])
# Суммируем население пи коругам
district_population[district] = district_population.get(district, 0) + pop
for district, total_pop in district_population.items():
print(f{di strict} округ: {total_pop} человек")
Вывод:
Центральный округ: 13400000 человек
Приволжский округ: 2400000 человек
Пример 2. Транспонирование данных
Список initial_data содержит данные об измерении температуры и влажности на разные даты. В необработанном виде он сохраняется в файл sensor reading-in.csv, а после транспонирования (даты становятся столбцами, а названия измерений - строками) - в файл sensor_reading_out .csv:
import csv
Работа с файлами 491
initial_data = [ ["Дата", "Температура1', "Влажность”], ["2025-11-01 , 29.5, 60], ["2025-11-02’, 21.0, 58], ["2025-11-03', 19.8, 62], ]
#	Записываем исходные данные в CSV-файл
with ooen(' sensor_readings_in.csv’ , "w", newline=" , encuding="utf-8 ) as infile:
writer = csv.writer(infiie) writer.writerows(initial_data)
#	Читаем содержимое этого файла all_rows = []
with open( sensor_readings_in.csv", "r", newline="", enccding="utf-8") as infile:
reader = csv.reader(infile) all_rows = list(reader)
#	Транспонируем список списков
#	zip(*aLL_rows) выполняет транспонирование (разворачивает строки в кортежи) transposed_rows = list(zip(*all_rows))
#	Записываем транспонированные данные
with ipen( sensor_readings_out.csv", "w", newline= , encoding='utf 8") as outfile:
writer = csv.writer(outfile) writer.writerows(transposed_rows)
for row in transposedrows: print (f"-( row}')
Вывод:
(’Дата1, 1025*11-01', '2025-11-02', '2025-11-03') ('Температура', '20.5', '21.0', '19.8') (‘Влажность’, '60', '58', '62')
Пример 3. Форматирование номеров телефонов
Словарь raw_contacts хранит контакты каждою из сотрудников, которые в необработанном виде записываются в файл raw_contacts.csv Однако номер телефона у всех указан по-разному, что осложняет интеграцию с другими системами, которые требуют строгого формата.
Каждый номер телефона из этого файла обрабатывается с помощью функции normalize_phone(phone_str), которая удаляет из номера phone_str все нецифровые символы и добавляет к нему префикс +7. После этого данные с
Работа с файлами 492
обработанными номерами телефонов записываются в новый файл clean_con-tacts.csv:
import csv
#	Создаём файл с исходными данными
with open( raw_contacts.csv', "w+", newline="", encoding= 'utf-8'') as raw_con-tacts_fiie:
raw_contacts = [
["Имя j "Телефон", "Email ],
["Алексей", "8-900-123-45-67", "a@mail.com"]
["Мария", "+7(912) 987 65 43", 'm@mail.com']
["Денис", "905-111-22-33", "d@mail.com"],
writer = csv.writer(raw_contacts_file)
writer.writerows(raw_contacts)
def normal!ze_phune(phone_str: str) -> str:
"""Очищает телефонный номер и приводит его к формату +7ХХХХХХХХХХ.
Параметры:
pnone_str: Неотформатирооанный номер телефона.
возвращает:
Номер телефона, приведенный к формату +7ХХХХХХХХХХ.
И •« к
digits =	.join(filter(str.isdigit, phone_str))
if digits.startswith( ):
digits = "7" + digitsfl:]
elif not digits.startswith( 7 ) and len(digits) == 10:
# Предполагаем, что это 10-значный номер без 7/8 digits = "7" + digits
if len(digits) == 11:
return f "+{digits}"
return phone_str # Возвращаем как есть, если не удалось нормализовать
#	Обрабатываем исходные данные
with ooen('raw_contacrs.csv', "г", newline= , encoding= utf-8 ) as raw^con
tacts file:
cleanedrows = [J it Список для обработанных строк reader = csv.reader(raw_contacts_file) header = next(reader) cleaned_rows.append(header) # Сохраняем заголовок
if Индекс столбца "Телефон" - 1
phone_inaex = header.index('Телефон') if "Телефон" in header else 1
Работа с файлами 493
for row in reader:
raw_phone = row[phone_indexj
row[phone_index] = normalizephone(rawphone)
cleaned_rows.append(row) print(-f-"-{raw_phone} -> {row[pbone_index]}’)
#	Записываем обработанные данные в новый файл
with >oen(' clean_contacts.csv", "w", newline="", encoding= utf-8") as clean_contacts_file:
writer = csv.writer(c]ean_contacts_file)
writer.writerows(cleaned_rows)
Вывод:
8-900-123-45-67 -> +79001234567 +7(912) 987 65 43 -> +79129876543 905-111-22-33 -> +79051112233
Итоги
J CSV-файл - это файл с расширением .csv для представления табличных данных, в котором каждая строка соответствует одной строке в таблице, а значения, составляющие столбцы, разделены специальным символом-разделителем, чаще всего запятой.
J Модуль csv предоставляет специальные объекты чтения и записи для работы с CSV-файлами, позволяющие работать с данными в формате списков или словарей.
V Объект чтения, созданный с помощью функции csv.reader() возвращает данные в виде списка, а с помощью класса csv .Di ctReader - в виде словаря
J Объект записи, созданный функцией csv.writer(), записывает в CSV-файл список, а классом csv .DictWriter - словарь.
J Метод writer .writerow() записывает в CSV-файл список или словарь как одну строку.
J Метод writ er.writerow() записывает в CSV-файл список списков или список словарей как несколько строк.
Задания для самопроверки
1.	Почему при работе с CSV-файлами не рекомендуется использовать стандартные методы чтения file.read() или file.readline()?
2.	Какой обязательный дополнительный параметр (кроме пути к файлу)
Работа с файлами 494
должен быть передан конструктору класса csv.DictWriter? Для чего он нужен?
3.	Создайте файл students.csv со следующим содержанием:
ФИО,Курс,Специальное!ь,Средний балл Толстой Лев Николаевич^,Филология,4.8 Достоевский Федор Михайлович,2,История,4.5 Чехов Антон Павлович,4,Медицина,4.9
Пушкин Александр Сергеевич,1,Журналистика,4.2
Гоголь Николай Васильевич,3,Искусствопедение,4.6
Откройте этот файл в режиме чтения, создайте объект чтения с помощью функции csv.reader(), и выведите на экран список всех специальностей.
4.	Откройте файл students.csv из задания 3 и создайте объект чтения с помощью класса csv.DictReader. Выведите на экран только ФИО и специальность для каждого студента.
5.	Добавьте в конец файла students .csv из задания 3 новый список ["Бунин Иван Алексеевич", 3, "Лингвистика", 5]. Выведите на экран список всех ФИО студентов.
7.3 РАБОТА С JSON-ФАЙЛАМ И
Формат JSON (от англ. JavaScript Object Notation - нотация объектов JavaScript) - это текстовый формат, который чаще всего используется для обмена данными в современных информационных системах.
JSON был создан как способ представления структур данных в JavaScript, но быстро стал популярным благодаря своей простоте, читаемости и лёгкой интеграции практически с любым языком программирования.
Особенности JSON-файлов
JSON оперирует двумя основными структурами, которые напрямую соответствуют основным коллекциям Python: объектами и массивами.
Объекты (англ, objects) представляют собой неупорядоченный набор пар «ключ: значение» и соответствуют словарям в Python.
{ "Фамилия": "Блок", "Имя": "Александр , "Отчество": "Александрович"
}
При этом ключи в объектах JSON всегда должны быть строками, Работа с файлами 495
заключенными в двойные кавычки.
Массивы (англ, arrays) являются упорядоченными списками значений, то есть соответствую)' обычным спискам в Python:
[
"Светское общество",
"Лишний человек",
"Дружба"
]
Также JSON позволяет работать не только со словарями и списками, но также со строками, числами, логическим и пустым типами.
Таблица 42 - Типы данных в JSON
Тип дан-	Тип дан-	Особенности	Пример
ных в JSON	ных в Python		
Объект	diet	Ключами могут быть только строки	{ "Фамилия": "Лермонтов", "Имя": "Михаил", "Отчество": "Юрьевич" }
Массив	list	Упорядоченный список значений	[ "Романтизм", "Свобода", "Исповедь" ]
Строка	str	Последовательность символов всегда заключается в двойные кавычки	"Жанр"
Число	int или float	Целые и вещее) венные числа	1833
Логический тип	bool	Логические значения пишутся строчными буквами: true и false	true
Пустой тип	NoneType	Пустое значение пишется строчными буквами: null	null
Работа с файлами 496
Для удобства чтения и легкого отслеживания пар «ключ-значение» в JSON-файле принято располагать каждый элемент (пару ключ-значение в объекте или элемент в массиве) на отдельной строке с использованием отступов. Рекомендуется использовать 2 или 4 пробела. Но с точки зрения компьютера переносы строк и отступы не являются обязательной частью структуры.
Теоретически вложенность объектов и массивов в формате JSON не ограничена и объект может содержать массив, который содержит объект и так далее, однако на практике вложенность редко превышает 3-5 уровней.
Давайте рассмотрим пример JSON-файла, содержащего информацию о книгах в библиотеке:
[ { "ID": "401", "Название": "Двенадцать", 'Автор": {
"Фамилия": "Блок", "Имя": "Александр", "Отчество": "Александрович"' Ъ "Год_публикации": 1918, "Жанр": “Поэма", "Ключевые_слова": [
"Революция", "Петербург", "Символизм" ] ъ { "ID": "402", "Название": "Мцыри", "Автор": {
"Фамилия": "Лермонтов", "Имя": "Михаил , "Отчество": "Юрьевич" Ъ "Год_публикации": 1840, "Жанр": "Поэма", "Ключевые_сл< >ва": [
"Романтизм", "Свобода", "Исповедь" ] Ъ { ”10": "403", "Название": "Евгений Онегин",
Работа с файлами 497
-Автор": { "Фамилия": "Пушкин", "Имя": "Александр’, "Отчество": "Сергеевич"
b
"Год_публикации": 1833, "Жанр": "Роман в стихах", "Ключевые_сл'ва": [
"Светское обществе", "Лишний человек", "Дружба"
]
}
]
Весь файл начинается и заканчивается квадратными скобками, что означает массив записей о книгах. Элементы в массиве разделяются запятыми. Каждый элемент внутри основного массива является объектом, описывающим одно произведение. При этом ключ "Автор" имеет в качестве значения не просто строку, а другой объект, а значением ключа "Ключевые_слова" является другой массив.
JSON и API
Чтобы понять, почему JSON так важен, необходимо разобраться в том. что такое API.
API (от англ. Application Programming Interface - интерфейс прщраммиро-ванпя приложения) - это набор правил и протоколов, который позволяет одной программе (клиенту) запрашивать данные или услуги у другой программы (сервера), не зная, как та программа работает внутри.
API похож на официанта в ресторане, где вы (клиент) не знаете как устроена кухня (сервер), но делаете заказ (отправляете запрос API) через официанта. Он передаёт заказ на кухню, которая готовит блюдо (генерирует данные по запросу), после чего официант приносит вам готовое блюдо (сервер отправляет ответ с данными).
Когда программа-клиент запрашивает у сервера, например, список последних новостей или погоду в городе, сервер должен вернуть эти данные в общепонятном и структурированном виде. Наиболее подходящим форматом для этого является JSON. поскольку он одновременно легко читается человеком и обрабатывается компьютером.
Работа с файлами 498
Модуль json
Стандартная библиотека Python имеет встроенный модуль json для работы с JSON-файлами. Операции чтения и записи в нём данных делятся по принципу: работаем мы с файлом на диске или с текстовой строкой в памяти. При этом преобразование объектов и структур данных в строку JSON называется сериали тайней, а обратный процесс, при котором строка JSON преобразуется обратно в объекты и структуры данных, называют десериализацией
Перед началом работы модуль json следует импортировать:
import json
Чтение и запись JSON-файлов
Если у вас уже есть JSON-файл, который нужно прочитать, то для этого в модуле json представлен метод json.load(), а для записи данных в новый или существующий JSON-файл существует метод json. dump().
Чтение JSON-файла
Давайте создадим простой файл food, json с данными, которые через API может возвращать сервис заказа продуктов питания:
[
{
"ID": "р001",
"Название": "Молоко цельное 3.2% ,
"Категория": "Молочные продукты'.,
"Цена_руб": 85.59
Е
{
"ID": "р002",
"Название": "Хлеб Бородинский",
"Категория": “Хлебобулочные изделия",
"Цена_руб": 60.00
Е
{
"ID": "р003',
“Название": "Батон",
"Категория": "Хлебобулочные изделия",
“Цена_руб": 79.00
}
]
Для того, чтобы прочитать JSON-файл, предназначена функция json.load(), которая считывав! его содержимое как список или словарь Python.
Работа с файлами 499
Функция
Описание
Параметры
json.load(file)
Считывает всё содержимое в JSON-файле file и преобразует его в список или словарь Python
• file - файловый объект
Во {вращаемое значение
Список или словарь
Ей достаточно передать файл, открытый в одном из режимов чтения, после чего она считывает весь поток символов из файла, анализирует его структуру, проверяет синтаксис JSON и строит в памяти соответствующий объект Python.
with jpen( food.json", "r", encuding="utf-8' ) as food_file food_lst = json.ioad(food_file) for fooa in food_lst:
print(food["Название ])
#	Вывод: Молоко цельное 3.2%
#	Вывод: Хлеб Бородинский
#	Вывод: Батон
Функция json.load() возвращает список или словарь, с которым можно работать как с любым таким объектом в Python, например, перебирать в цикле for или обращаться по ключу.
Это позволяет удобно обрабатывать данные, полученные из внешних источников. без необходимости вручную разбирать текст файла. Соответствие структуры JSON структурам данным в Python делает работу с такими файлами наглядной и предсказуемой.
Запись в JSON-файл
Списки и словари в Python можно записать в JSON-файл с помощью функции json.dump().
По умолчанию функция json.dump() записывает все не-ASCII символы, такие как русские буквы, используя Юникод-экранирование. Например, русская буква «д» имеет код U+0434 и будет записана в JSON-файл как \u0434. Для того, чтобы сохранить кириллицу в читаемом виде, следует передать функции аргумент ensure_ascii=False.
Работа с файлами 500
Функция
Описание
Параметры
Возвращаемое значение
json.dump(obj, file, ensure_ascii=True, indent=None)
Записывает список или словарь obj в файл file
•	obj - записываемый список или словарь
•	file - файловый объект
Необязательные параморы:
•	ensure_ascii - экранирование не-ASCII символов. По умолчанию все не-ASCII символы, например, русские буквы, экранируются
•	indent - количество отступов. По умолчанию запись идёт в одну строку
None
Если не задать количество отступов с помощью параметра indent, то функция json.dump() запишет данные в одну строку. Это не очень удобно для чтения, поэтому рекомендуется использовать 2 или 4 отступа, что не влияет на структуру данных, а изменяет только внешний вид JSON-файла: meeting_data = { "Ю_События": 101, "Дата**: “2025-11-25", "Время_начала": "14:00", "Бремя_окончания": "15:30", "Название": "Встреча с отделом продаж', "Статус": "Подтверждено", "Участники": ["Большой босс", "Средний босс", "Маленький босс"] }
with open( meeting.json , "w+", encoding='utf-8") as meeting: json.dump(meeting_data, meeting, indent=2, ensure_ascii=False)
# Читаем записанные данные meeting.seek(P) data = json.load(meeting) print(data["y4acTHHKH']) if Вывод: ['Большой босс'; 'Средний босс'; 'Маленький босс']
Работа со строками
Модуль json позволяет работать не только с JSON-файлами, но и с обычными строками в формате JSON. Для этого предназначены функции
Работа с файлами 501
json.loads(), которая преобразует строку в JSON-формате в объект Python, и функция json.dumps(), которая наоборот, преобразует объект Python в такую строку.
Названия эти функций отличаются от функций работы с JSON-файлами только буквой «s» (от англ, string - строка) на конце, так как они выполняют ту же самую работу, но в качестве носителя данных используют не файл, а обычную строку в оперативной памяти.
Преобразование строки в формате JSON в объект Python
Чаще всего ответ, полученный через API, представлен в виде обычной строки Python, и для того, чтобы обращаться к её элементам по ключу или по индексу, такую строку необходимо преобразовать в словарь или список с помощью функции json.loads().
Функция
Описание
Параметры
json.loads(json_string)
Преобразует строку в JSON-формате json_string в список или словарь Python
• json_string - строка в формате JSON
Возвращаемое значение
Список или словарь
Эта функция выполняет синтаксический анализ переданной ей строки и возвращает объект Python:
json_string = '{"Город": "Череповец", "Координаты": {"Широта": 59.1333, "Долгота”: 37.9000}}'
city = json.]oads(json_string)
print(city["Город"])
# Вывод: Череповец
Главное отличие функции json.loads() от функции json.load() заключается в том, что она работает исключительно со строками в памяти и не взаимодействует с файловой системой.
Преобразование в строку в формате JSON
Как строку в формате JSON можно преобразовать в объект Python, так и наоборот список или словарь можно преобразовать в строку в этом формате с
Работа с файлами 502
помощью функции json.dumps().
Функция	json.dumps(obj, ensure_ascii=True, indent=-None)
Описание	Преобразует список или словарь obj в строку в формате JSON • obj - записываемый объект Python 11еобязателытые параметры:
Параметры	•	ensure_ascii - экранирование не-ASCII символов. По умолчанию все не-ASCll символы, например, русские буквы, экранируются •	indent - количество отступов. По умолчанию запись идёт в одну строку
Возвращаемое значение	Строка
Она принимает такие же параметры как и функция json.dump(),TO есть для работы с кириллицей ей нужно запретить Юникод-экранирование с помощью аргумента ensure_ascii=False и задать количество отступов indent для читаемого вида-
user_info = {"ID": 105, "Активен": False, "Роль": "Гость , "Язык": "Русским } json_user_in-fо = json.dumps(user_info, ensure_ascii=False, indent=2) print(json_user_info)
# Вывод: {
#	"ID": 105,
#	"Активен": false,
#	"Роль": "Гость",
#	"Язык": "Русский"
# }
print(type(json_user_info))
# Вывод: <cLass “str“>
Это позволяет получить красиво форматированную строку для вывода на экран или дотирования.
Примеры
Пример I. Получение данных о местоположении пользователя
Геокодирующий сервис определяет местоположение пользователя по IP и возвращает его в виде следующего JSON-файла:
Работа с файлами 503
{
"status : "success",
"queryTime": "2025-11-30T14:30:00Z",
"address": {
"country’: "Россия",
"city": "Москва", "coordinates": {
"lat": 55.7558,
"Ion": 37.6173 b
"street": "Тверская ул." b "ip": "8.8.8.8"
}
Программа открывает этот файл и извлекает из него нужные данные:
import json
with open("geoip_response.json", "г", encoding="utf-8") as response: responsejdata = json.load(response)
city = response_data["address ]["city"]
latitude = resporise_daca[ "address ]["coordinates"] ['lat"] status = response_data.get("status", "error1)
print(f"CTaTyc запроса: {status}") print(f Город: {city} ) print(f Координаты: Широта {latitude} )
Вывод:
Статус запроса: success Город: Москва
Координаты: Широта 55.7558
Пример 2. Создание файла с настройками
Настройки приложения хранятся в словаре conf ig_data, однако настройки часто редактируются вручную системными администраторами или пользователями. поэтому этот словарь записывается в файл app_config. json:
import json
config_data = { "database": {
"host": "localhost", "port": 5432, "user": "admin_user" b "logging": {
Работа с файлами 504
"level": "INFO", "destination": "/var/log/app.log”, "language": "Русский"
}, "max_threads": 8 }
with open( ”app_config.json' , "w+", encoding="utf-8 ) as config: json.duinp(config_data, config, indent=2, ensure_ascii=False) config.seek(0) print(config.read(J)
Вывод:
{
"database": {
"host": "localhost",
“port": 5432,
"user": "admin_user"
b
"logging": {
"level": "INFO",
"destination": "/var/log/app.log", "language": "Русский"
Ъ
"max_threads": 8
}
Пример 3. Преобразование JSON в CSV
В системе данные о статусе проекта и ответственном менеджере хранятся в файле projects, json с примерно следующим содержанием:
[
{ "id": 1, "name": "Мобильное приложение", "manager": "Ломоносов М.В.", "status": "Активный"
Ъ
{ "id": 2, "name": "06hi вление сервера”, "manager": "Романов П.А.", "status": "Завершённый"
Ъ
{ "id": 3, "name" : "Внедрение CRM", "manager": "Курчатов И.и. , "status": "Активный"
}
Работа с файлами 505
]
Хотя JSON является стандартом для передачи данных, но CSV до сих пор необходим для простой аналитики или загрузки в Excel. Поэтому данные из этого файла читаются, обрабатываются и записываются в CSV-файл.
Для этого в контекстном менеджере одновременно открывается оба файла: import json import csv
with open( projects, json , "r‘, enccding="utf-8") as in-file, \
open("projects_report.csv', "w", newline*'"', encoding* utf-8 ) as outfile: # Загружаем данные JSON projects = json.load(infile)
#	Создаём объект записи CSV csv^writer = csv.writer(odtfile)
#	Заголовки для CSV (выбираем только нужные поля) csv_headers = ["ID", ’Название", "Менеджер"] csv_writer.writerow(csv_headers)
#	Записываем данные из JSON в CSV tor project in projects:
# выбираем и записываем только активные проекты if p^oject["status"] == "Активный":
row = [
project[“id"],
project["name' ], project["manager ] ] csvwriter.writerowf row) print(f"Экспортирс’ьан проект: {project['name ]}") else:
prinr(f"Пропущен проект: {project['name ]} (Статус: {project^ status ' ]})")
Вывод:
Экспортирован проект: Мобильное приложение
Пропущен проект: Обновление сервера (Статус: Завершённый)
Экспортирован проект: Внедрение CRM
Содержимое файла projects_report.csv:
ID,Название,Менеджер
1,Мобильное приложение,Ломоносов М.В.
3,Внедрение CRM,Курчатов И.В.
Работа с файлами 506
Итог и
J JSON-файл - это файл с расширением .json. который чаще всего используется для обмена данными.
J JSON основан на объектах (словарях) и массивах (списках).
J Модуль json предоставляет инструменты для работы с JSON-файлами.
✓ Прочитать JSON-файл позволяет функция json.load(). а записать в него данные - функция json.dump(),
J Преобразовать строку в JSON-формате в объект Python позволяет функция json.loads(), а объект Python втакуюсгроку - функция json.dumps().
Задания для самопроверки
1.	Назовите две основные структуры данных в JSON и укажите, каким типам данных Python они соответствуют.
2.	В чем состоит отличие между парой функций json. load() и json.dump() и парой функций json.loads() и json.dumps()?
3.	Создайте новый файл user, json и запишите в него словарь user_data = { "id": 1, "username": "coder_pro", "active": True }.
4.	Откройте файл user, json из предыдущего задания, прочитайте его содержимое и выведите на экран значение ключа "username".
5.	Дан список books = [{"title": "Преступление и наказание", "year": 1866}, {"title": "Война и мир", "year": 1869}]. Преобразуйте его в строку в JSON-формате, проверьте её на принадлежности типу строк (str) и выведите результат на экран.
7.4 СОХРАНЕНИЕ И ВОССТАНОВЛЕНИЕ ОБЪЕКТОВ
Текстовые форматы, например, CSV и JSON могут сериализовать (преобразовать в текст) только базовые типы: строки, числа, списки и словари, однако они не позволят сохранить данные более сложных типов, например, экземпляры классов.
Модуль pickle был создан специально для решения этой проблемы. С английского слово «pickle» переводится как «соленья», что является метафорой для «консервирования» данных.
Работа с файлами 507
Ключевой особенностью модуля pickle является использование двоичного формата хранения данных. Он не пытается быть читаемым человеком, а сосредоточен на сохранении внутренней структуры объекта. По этой причине файлы, созданные с помощью pickle, всегда открываются в двоичном режиме (с буквой «/?» в конце, например, "rb" или "wb”).
Перед началом работы модуль pickle следует импортировать: import pickle
Сохранение и восстановление объектов в файлах
Модуль pickle работает с файлами с расширением .pkl, то есть он может как записать в такой файл состояние объекта с помощью функции pl ckle.dump(), так и прочитать его, то есть восстановить записанный объект с помощью функции pickle.load().
Сохранение состояния объекта в файл
Использование функции pickle.dump() является самым простым способом для сохранения сложной структуры данных Python.
Функция
Описание
Параме1ры
Возвращаемое значение
pickle.dump(obj, file)
Преобразует объект obj в последовательность байтов и записывает её в файл file
* obj - объект Python, который требуется сохранить
♦ file - файловый объект
None
Одним из главных преимуществ модуля pickle является возможность сохранять экземпляры классов. Допустим, у нас есть класс Artist для представления информации о художнике: class Artist:
def___init__(self, name: str, style: str);
self.name = name self.style = style
def get_info(self) -> str:
return f"{self.name}, стиль: {self.style}"
Работа с файлами 508
Мы можем создать экземпляр этого класса и сохранить его в файл с расширением . pkl:
shishkin_obj = Artist( Шишкин В.В.", "Реализм')
with open( shishkin.pkl", “wb1) as shishkin_file:
# Сохраняем не только данные, но и принадлежность к классу Artist pickle.dump(shishkin_obj, shishkin_file)
После выполнения этого кода в папке с программой появится файл shish-kin.pkl с сохраненным экземпляром класса Artist.
Важно понимать, что модуль pickle сохраняет только сами данные и информацию о типе, но не код класса. Другими словами, он сохраняет состояние экземпляра класса, то есть значения всех его атрибутов и включает в себя все данные, которые были присвоены переменным внутри объекта, например, self. name или self. style. Однако методы являются частью определения класса, а не конкретного экземпляра, поэтому они не сохраняются.
В дополнение к данным, модуль pickle записывает в двоичный поток информацию о том, к какому классу принадлежит этот объект, например, <class '__main__.Artist' >. Эта информация является ссылкой на определение класса в
коде Python.
Восстановление сохранённою обьекта из файла
Для восстановления сохраненного объекта необходимо использовать функцию pickle.load().
Функция	pickle.load(file)
Описание	Восстанавливает объект Python из потока байтов в файле file
Параметры	• file - файловый объект
Возвращаемое значение	Восстановленный объект
Давайте восстановим объект из файла shishkin.pkl, который мы сохранили ранее:
with open( shishkin.pkl", "rb’ ) as shishkin_file: shishkin_restored_jbj = pickle.load(shishkin_file) print(shishkin_restored.obj.name) # Вывод: Шишкин В.В.
Работа с файлами 509
Как видно, объект восстановлен успешно, так как мы можем обратиться к его атрибутам.
При восстановлении объекта Python считывает из файла информацию о типе и пытается найти определение этого класса в текущем пространстве имён или среди импортированных модулей. После этого он создаёт новый, пустой экземпляр найденного класса и заполняет его атрибуты сохраненными значениями, извлеченными из файла.
Работа со с 1 рока ми
Модуль pickle позволяет работать с объектами без непосредственного сохранения их в файл на диске, сохраняя и восстанавливая их через байтовую строку в памяти с помощью функций pickle.dumps() и pickle.loads() соответственно.
Эти функции выполняют ту же самую работу, что и функции для работы с файлами pickle.dump() и pickle. load(), но используют в качестве носителя данных не файл, а объект типа bytes в оперативной памяти.
Сохранение состояния объекта в байтовую строку
Функция pickle.dumps() принимает обьект Python и преобразует его в последовательность байтов bytes, возвращая её как результат.
Функция	pickle, dumps(obj)
Описание	Преобразует объект	obj	в	последовательность байтов
Параметры	• obj - объект Python,	который 1ребуется сохранить
Во.врашаечое БаПтовая
строка значение
Чаще всего эта функция применяется для подготовки данных для передачи по сети, всфаивания в базу данных или передачи данных между процессами внутри одной программы.
Давайте создадим ещё один экземпляр класса Artist и преобразуем его в последовательность байтов:
aivazovsky_obj = Artist( Айвазовский И.К. , "Романтизм")
binary_payload = pickle.dumps(aivazovsky_obj)
Работа с файлами 51(1
print(binary_payioad)
#	Вывод;
b'\x80\X04\x95c\x00\x00\x00\x00\X00\x00\x00\x8c\X08_ma in_\x94\x8c\X06Art-
ist\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\xla\xd0\x90\xd0\xb9\xd0\xb2\xd0\ xb0\xd0\xb7\xd0\xbe\xd0\xb2\xdl\x81\xd0\xba\xd0\xb8\xd0\xb9
\xd0\x98.\xd0\x9a.\x94\x8c\x05s tyle\x94\x8c\xl2\xd0\хав\xd0\xbe\xd0\xbc\xd6\xb0\xd0 \xbd\xdl\x82\xd0\xb8\xd0\xb7\xd0\xbc\x94ub. '
print(type(binarypayload))
#	Вывод: <class 'bytes'>
Результат всегда имеет тип bytes (байтовая строка), а не str (текстовая строка), поскольку модуль pickle работает только с двоичными файлами.
Восстановление объекта из байтовой строки
Функция pickle.loads() принимает байтовую строку, содержащую данные pickle, и восстанавливает исходный объект Python в памяти.
Функция	pickle, lcads(data)
Описание	Восстанавливает объект из байтовой строки data
Параметры	• data - байтовая строка
Возвращаемое Восстановленный объект значение
Эта функция применяется для восстановления объекта, который был сериализован с помощью функции pickle .dumps() или получен из внешнего источника, например, как ответ сервера.
Давайте восстановим байтовую строку binary_pay load, в которую мы ра-
нее сохранили экземпляр класса Artist:
aivazoi/sky_restored j>bj = pickle.loads(binary_payload)
print(aivazovsky_restored_obj.name)
#	Вывод: Айвазовский И.К.
print(type(aivazovsky_restored_obj))
#	Вывод: (class '_main__.Artist’>
Как видно, объект полностью восстановлен, так как мы можем обращаться к его атрибутам, а его тип определяется как экземпляр класса Artist.
Работа с файлами 511
Безопасное! ь при восстановлении данных
Никогда не используйте функцию pickle. load() для восстановления данных. полученных из недостоверных источников, так как они могут содержать вредоносные инструкции.
Модуль pickle следует использовать только для обмена данными между процессами, которые вы полностью контролируете, например, для сохранения состояния вашей собственной программы.
Примеры
Пример I. Восстановление множества тегов
Класс Article описывает статью, у которой есть название title и множество уникальных тегов tags. JSON не поддерживает напрямую тип множества set и при сериализации преобразует его в список, теряя семантику уникальности элементов. Модуль pickle сохраняет тип объекта, гарантируя, что при восстановлении атрибут tag_set останется множеством:
import pickle
class Article:
...Описывает статью с уникальными тегами.....
def __init__(self, title: str, tags: set):
..Конструктор класса Article.
Параметры:
title: Заголовок статьи.
tags: Множество уникальных тегов.
self.title = title
self.tagset = {tag.lower() for tag in tags}
def aad_tag(self, tag: str) -> None:
"""Добавляет новый тег в множество.""" self.tagset.add(tag.lower())
article_obj = АгМс1е("0сновы Python", {"Программирование", Учеба }) article_obj.aad_tag( Python") # Добавляем тег
article_obj.aad_tag( Учеба') # Тег "учеба" уже есть, дубликат не добавится
#	Сохраняем экземпляр класса
with open('article_object.pkl , wb‘) as article_object: pickle.dump(article_obj, article_object)
Работа с файлами 512
#	Восстанавливаем экземпляр класса
with ooen("article_object.pkl , "rb") as article_object: restored_article = pickle.load(article_object)
#	Проверяем, что множество сохранилось
print(-Г'Теги после восстановления: {restored_article.tag_set}')
Вывод:
Теги после восстановления: {'программирование', 'учеба', 'python'}
Пример 2. Сохранение нескольких объектов настроек в одном файле
Класс Config упрощённо описывает настройки в системе, а его экземпляр configobj представляет настройки логирования Кроме этого, в программе определён список user_list с именами пользователей и словарь metadata с метаданными. Для удобства архивирования состояния программы все объекты сохраняются в одном файле multi_archive.pkl. Однако восстановление должно происходить строго в том же порядке, в котором они были записаны, для того чтобы функция pickle.load() могла правильно восстановить каждый объект: import pickle
class Config:
..описывает настройки системы.....
def___init__(self, key: str, value: str):
"""Конструктор класса Config.
Параметры:
key: Название настройки.
value: Значение настройки. II II II
self.key = key self.value = value
config obj = Config( LOG_LEVEL", "DEBUG")
user_list = ["alastoi-77’, “big_vox", "adam999"]
metadata = {"Количество записей": 3, "Количество комментариев": 2}
#	Последовательно сохраняем
with ореп('multi_archive.pkl", "wb") as multi_archive:
pickle.dump(config_obj, multi_archive)
pickle.dump(user_list, multi_archive)
pickle.dump(metadata, multi_archive)
#	Последовательно восстанавливаем
Работа с файлами 513
with open('mj1ti_archive.pк1"j "rb") as multi_archive: restored_config = pickle.load(multi_archive) restored_user_list = pickle.load(multi_archive) restored_metadata = pickle.load(multi_archive)
print(f Восстановленный объект 1: {restored_config.key} {restored_con-fig.value}')
print(f Восстановленный объект 2: Последний пользователь: {rest-)red_user_list[-l]}’ )
print(f Восстановленный объект 3: Всего записей. {restored_metadata['Количество записей' ]}")
Вывод:
Восстановленный объект 1: LOG_LEVEL: DEBUG
Восстановленный объект 2: Последний пользователь: adam999
Восстановленный объект 3: Всего записей: 3
Пример 3. Сохранение состояния прот раммы
В задачах анализа данных или машинного обучения промежуточные результаты, такие как обученные модели или предварительно обработанные большие наборы данных, могут быть получены после многих часов работы. Модуль pickle позволяет сохранить весь контекст как есть (включая сложные объекты Python), а затем мгновенно его восстановить.
Вместо того, чтобы каждый раз открывать файл и записывать в него данные, в программе определена функция save_context(context, filename), которая сохраняет словарь context в файл filename, и функция load_context(file-name), которая восстанавливает объекты из этого файла:
import pickle
from datetime import datetime
def save_contexc(c intext: diet, filename: str) -> None: """Сохраняет сливарь с рабочим контекстом в файл.
Параметры:
context: Словарь, с состоянием программы (переменные, данные), filename: Имя файла, в который сохраняется контекст. II II II
with open(filename, "wb") as file: pickle.dump(context, file) print(f Данные сохранены в файл {filename}")
def load_context(filename: str) -> diet:
"""Восстанавливает словарь с рабочим контекстом из файла.
Работа с файлами 514
Аргументы:
filename: Имя файла, из которого восстанавливается контекст.
возвращает;
Восстановленный словарп с состоянием программы. II И II
with open(filename, rb") as file:
context = pickle.load(file)
print(f Данные восстановлены из файла {filename}") return context
it имитация сложного рабочего процесса project_context = {
"step_name": "Fearure Engineering", "dataset_size": 15_000, "processed_cols": ["age", "income", "city_id"], "is_model_trained": False, "last_run": datetime.now()
}
#	Сохраняем данные
save_context(pr,oject_context, -data_pipeline_context.pkl )
#	Восстанавливаем данные
restored_context = load_contexr("data_pipeline_context.pkl")
print(f восстановленная дата запуска: {restored_context['last_run']} ) print(f"b >ccTaHoeneHHbie имена столбцов: {restored_context[‘pro-cessed_cols *]}')
Вывод:
Данные сохранены в файл data_pipeline_context.pkl
Данные восстановлены из файла data_pipeline_context.pkl ► остановленная дата запуска: 202S-12-01 00:06:49.297822 восстановленные имена столбцов: ['age', 'income', 'city_id']
Итоги
J Модуль pickle предоставляет функции для сохранения и восстановления объектов Python.
Функция pickle.dump() преобразует объект в байтовую строку и записывает её в файл с расширением .pkl. а функция pickle.load() восстанавливает объект из этого файла.
Функции pickle.dumps() и pickle.loads() выполняют ту же самую работу, но используют в качестве носителя данных не файловый объект, а объект типа bytes в оперативной памяти.
Работа с файлами 515
J He рекомендуется использовать функцию pickle.load() для восстановления данных, полученных из недостоверных источников, так как они могут содержать вредоносные инструкции.
Задания для самопроверки
1.	В каком формате модуль pickle сохраняет данные? Какие есть особенности открытия этого файла с помощью функции ореп()?
2.	Можно ли восстановить экземпляр класса из файла, если код этого класса не определён в текущем пространстве имён?
3.	Дан следующий класс, описывающий задачу в системе: class Task:
...Описывает задачу в системе....
def___init___(self, title: str, due_date: str, status: str- процессе"):
.Конструктор класса Task.
Параметры:
title: Название задачи.
due_date: Крайний срок.
status: Статус. По умолчанию 'В процессе'
sel-г. title = title
self.due_date = due_date self.status = status
def comp]ete(self) -> None:
"""Отмечает задачу завершённой, self.status = "Завершена”
Создайте экземпляр этого класса taski = Task("Написать отчёт", "2025-12-05") и сохраните его в файле taskl.pkl.
4.	Используйте файл taskl. pkl из задания 3 и восстановите объект из него. Выведите на экран атрибут title этого объекта.
5.	Преобразуйте восстановленный объект из задания 4 в байтовую строку и выведите на экран её тип.
7.5 ВЗАИМОДЕЙСТВИЕ С ФАЙЛОВОЙ СИСТЕМОЙ
Модель os (от англ. Operating System - операционная система) является
Работа с файлами 516
одним из наиболее часто используемых модулей в Python. Он предоставляет программам возможность взаимодействовать с операционной системой (ОС), на которой они запущены.
Это означает, что с помощью модуля os вы можете выполнять тс же операции. которые обычно делаете через командную строку или Проводник, но программно. прямо из вашего кода Python,
Главное преимущество модуля os заключается в его кроссплатформенно-сти: он скрывает различия между операционными системами. Например, функция os. remove() удалит файл как в W indows, так и в Linux, хотя базовая системная команда для этого на этих ОС. разная. Это позволяет писать код один раз, и он будет работать везде, где установлен Python.
Перед началом работы модуль os следует импортировать:
import os
Создание папки
Для создания новой папки в модуле os используются две основные функции: os.mkdir() и os.makedirs(). Выбор между ними зависит от того, нужно ли создать только одну папку в конце пути или в нём есть другие несуществующие папки.
Функция os.mkdir() используется только в том случае, если родительский каталог (папка, в которой создаётся новая папка) уже существует.
os.mkdir(path)
Создаёт последнюю папку в пути path
• path - абсолютный или относительный путь к создаваемой папке
None
значение
Параметру path можно передать как абсолютный путь (начиная с буквы диска), так и относительный (от текущего скрипта). Например, если мы хотим создать новую папку в той же папке, в которой находится наша программа, достаточно передать функции os.mkdir() только её имя:
os.mkdir(’data")
Функция
Описание
Параметры
Возвращаемое
Работа с файлами 517
# Папка создана
Этот код создаст в панке проекта новую папку data. Однако эта функция создаёт только последнюю папку в пути path, поэтому все остальные папки по этому пути должны существовать, иначе будет вызвано исключение FileNot-FoundError:
os.mkdir( ’src/images")
# Ошибка: FiLeNotFoundError: [WinError 3] Системе не удается найти указанный путь: 'src/images '
Эту проблему решает функция os.makedirs(), которая создает все папки, необходимые для формирования указанного пути.
Функция	os .makedirs(pathj exist_.ok^False)
Описание	Создаст все несуществующие папки в пути path • path - абсолютный или относительный путь к создаваемой папке
Параметры
Необязательные параметры:
• exist_ok - определяет поведение при существовании последней папки в пути path. По умолчанию ех-ist_ok=False, что вызывает исключение FileExistsError
Возвращаемое
None
значение
Эта функция используется для создания сложных длинных путей или в случае, если вы не уверены, существуют ли все папки в пути path. Поэтому с её помощью создать папку images в несуществующей папке src:
os.makedirs("src/images )
#	Папка создана
Если создаваемая папка уже существует, то по умолчанию обе функции os.mkdir() и os .makedirs () вызовут исключение FileExistsError. Однако функция os.makedirs() позволяет изменить это поведение с помощью аргумента ех-ist_ok=True.
os.makedirs("src/images , existoK=True)
#	Папке уже существует, но исключение не вызвано
Это гарантирует существование нужной панки.
Работа с файлами 518
Удаление папки
Модуль os предоставляет функцию os.rmdir(). которая удаляет пустую существующую папку.
Функция	os.rmdir(path)
Описание	Удаляет пустую папку по пути path
Параметры	• path - путь к удаляемой папке
Возвращаемое значение	None
Этой функцией можно удалить папку, только если в ней пет ни файлов, ни других панок, то есть она абсолютно пустая. Например, мы можем удалить ранее созданную пустую папку data, но если в папке src/images будет хотя бы одно изображение, то будет вызвано исключение OSError:
os.rmdir( 'data")
#	Папка удалена os.rmdir( 'src/images )
#	Ошибка: OSError: [WinError 145] Папка не пуста: 'src/images '
Если нужно удалить папку со всем её содержимым (файлами и подпапками), необходимо использовать функцию shutil.rmtree() из модуля shutil (от англ. Shell Utilities - утилита командной оболочки). Он также является одним из модулей стандартной библиотеки, поэтому его можно импортировать без установки:
import shutil
Функция shutil.rmtree() рекурсивно обходит папку и удаляет всё содержимое, а затем удаляет и саму папку.
Функция
shutil.rmtree(path)
Описание	Удаляет папку вместе со всем содержимым по пути path
Параметры • path - путь к удаляемой папке
Во {вращаемое
None
значение
Работа с файлами 519
Операция удаления с помощью этой функции необратима, поэтому её следует использовать с осторожностью: shutil.rmtree( src/images" ) # Папка удалена
Переименование файла и папки
Для переименования или перемещения файлов и папок в модуле os представлена одна универсальная функция os. rename().
Функция	os.rename(sre, dst)
Описание
Параметры
Переименовывает или перемещает файл или папку, изменяя к ней путь с sre на новый путь dst
•	sre - исходное имя (путь) файла или папки
•	dst - новое имя (путь) файла или папки
Возвращаемое Nor)i, значение
Если исходный путь sre и новый путь dst указывают на одну и ту же папку, функция выполняет переименование файла:
#	Создадим новый файл caLcuLations.txt with open( data/calculations.txt", "w+", encoding="utf- ) as data: data.write( Записываем строку в новый файл )
#	Переименовываем файл caLcuLations.txt в resuLts.txt os.rename( ’data/calculations.txt", 'data/results.txt")
Если новый путь dst указывает на друтую папку но сравнению с исходным путем sre, то функция выполняет перемещение файла в новую папку:
os.rename( data/results.txt', "src/results.txt")
# Файл resuLts.txt перемещён из папки data в папку sre
Удаление файла
Если создание файла происходит при вызове встроенной функции ореп() с режимом записи или добавления ("w" или "а"), то его удаление осуществляется через функцию os.remove().
Работа с файлами 520
Функция
Описание
Параметры
Возвращаемое значение
os.remove(path)
Удаляет файл по пути path
• path - путь, по которому удаляется файл
None
Не забывайте, что для удаления папок предназначены функции os. rmdir() и shutil.rmtree(), а функция os.remove() удаляет только файлы:
os.remove(' src/results.txt")
# Файл resuLts.txt удален
Объединение путей
В разных операционных системах используются разные символы для разделения папок в пути:
о Windows использует обратный слеш (\), например, C:\Users\Documents.
о Linux и macOS используют прямой слеш (/), например, /home/user/documents.
Если вы будете вручную собирать путь, используя жестко заданный разделитель, ваш код будет работать только на той операционной системе, для которой вы его писали. Однако функция os.path, join() из подмодуля os.path автоматически использует правильный разделитель, специфичный для текущей операционной системы, чтобы соединить компоненты пути, переданные ей в качестве аргументов.
Функция	os.path.join(path, *paths)
Описание	Соединяет путь path с компонентами пути paths в один
полный пуль
„	• path - начальный путь
Параметры	J
• *paths - компоненты пути
Возвращаемое Строка
С ПОЛНЫМ путём значение
Эта функция принимает две или более строки, которые представляют
Работа с файлами 521
части пути, и возвращает корректно сформированный путь: result_path = os.path.join("reports", "2025", "summary.csv") рг1пг(СРезультат: (result_path| )
#	Вывод, если код запущен в windows: reports\2e25\summary.csv
#	Вывод, если код запущен в Linux/macOS: reports/2025/summary.csv
Примеры
Пример I. Создание структуры проекта
Функция create_project_structure(base_dir, sub_dirs) принимает корневую папку проекта base_dir и список подпапок sub_dirs, которые требуется создать внутри неё. Этот список содержит вложенные списки, каждый из которых задаст путь к подпапке в виде последовательности папок. Функция автоматически создаёт как корневую папку проекта, так и все указанные вложенные папки, если они ещё не существуют: import os
def сreate_project_structure(base_dir: str, sub_dirs: list) -> None: """Создает заданную многоуровневую структуру папок, гарантируя, что все необходимые родительские папки существуют.
Параметры:
base_dir: Корневая папка проекта.
sub_dirs: Список относительных путей для создания. II II II
#	Создаем вазовую папку, если она не существует os.makedirs(base_diг, existsok=True)
for sub in sub_dirs:
# os.path.join обеспечивает кросс-платформенную совместимость sub_path = os.path.join(*sub) fuli_path = os.path.join(base_dir, sub_path) os.makedirs(full_path, exist_ok=True) print(f"Создан путь: (full_path) )
folders = [ ["input", raw"J, ["output , "processed"], ["logs", "daily"] ]
create_project_structure('prujectanalysis", folders)
Вывод:
Создан путь: project_analysis\input\raw
Работа с файлами 522
Создан путь: project_analysis\output\processed
Создан путь: project_analysis\logs\daily
Пример 2. Переименование файла
Функция rename_file(dir, old_name, prefix, index) переименовывает файл old_name в папке dir, добавляя ему префикс prefix и порядковый номер index:
import os
def rename_file(dir: str, old_name: str, prefix: str, index: int) -> None: .......Переименовывает файл, добавляя к его имени префикс и порядксвый индекс.
Параметры:
dir: Папка, где находится файл.
old_name: Текущее имя файла.
prefix: Новый префикс для имени файла, index: Порядковый индекс для имени файла. «( М Н
old_path = os.path.join(dir, old_name)
new_name = f"{prefix}_{index:03}_{old_name}"
new_path = os.path.join(dir, new_name) if not os.path.exists(old_path):
print(f"Файл {old_path} не найден") return
□s.rename(old_path, new_path) print(f"Переименовано. {old_path} -> {new_path}")
#	Создаём папку и файл для примера
os.naked irs("temp_files", exist_ok=True)
temp_path = os.path.join( "temp_fil.es", "data.csv")
with open(temp_path, "w") as data_file:
data_file.write("test )
#	Переименовываем файл
rename_and_index_file("ternp_files , data.csv", "processed , 1)
Вывод:
Переименовано: temp_files\data.csv -> temp_files\processed_001_data.csv
Пример 3. Перемещение файла в архив
Функция archive_file(work_dir, archive dir, filename) перемещает
Работа с файлами 523
обработанный файла filename из рабочей папки work_dir в архив archive_dir. Перед перемещением функция проверяет, существует ли файл, и создаёт папку архива при необходимости:
import os
def archive_file(work_dir: str, archive_dir: str, filename: str) -> None:
II 11 II
Перемещает файл из рабочей папки в архив.
Параметры:
work dir: Папка с рабочими файлами.
archive_dir: Папка архива.
filename: Имя файла для перемещения. II II II
src_path = os.path.join(work_dir, filename)
dst_path = os.path.join(archive_dir, filename)
if not os.path.exists(src_path):
print(f"®ana не найден: {src_path}") return
as.makedirs(archive_dir, exist_ok=True)
□s.rename(src_path, dst_path)
рг!пГ(Т"Файл перемещён в архив: {dst_path} )
#	Создаем папку и файл для примера os.rnakedirs( reports", exist_ok=True) file_dir = os.path.join("reports", 'monthly_report.txt ) with open(file_dir, "w‘, encoding="utf-8") as report_file:
report_file.write('*GT4eT за месяц")
#	Перемещаем файл д архив archive_file("reports", "reports_archive", "monthly_report.txt")
Вывод:
Файл перемещён в архив: reports_archive/monthly_report.txt
Ито1 и
J Модуль os предоставляет функции для взаимодействия с операционной системой.
Для создания новой папки в модуле os используются функции os.mkdir() и os. makedirs (). Первая используется только если родительская папка уже существует, а вторая создаёт все несуществующие папки, необходимые Работа с файлами 524
для формирования пути к создаваемой папке.
Функция os.rmdir() удаляет пустую существующую папку.
S Функция shutil.rmtreeO рекурсивно обходит папку и удаляет всё содержимое. а затем удаляет и саму папку.
Функция os. rename() переименовывает файлы и папки пли перемешает их, если изменяется путь, есть изменяет путь объекта с одного на другой.
J Функция os. remove () удаляет файл.
Задания для самопроверки
1.	В чём заключается различие между функциями os.mkdir() и os.make-dirs()?
2.	Как в Python удалить папку вместе со всем её содержимым?
3.	Используя функции модуля os. создайте папку с именем logs, а затем удалите её. После каждой операции выводите на экран сообщение с её описанием.
4.	Программно создайте файл с именем config_old.ini, затем с помощью одной из функций модуля os переименуйте этот файл в config.ini.
5.	Напишите функцию delete_dir(path), которая принимает путь path и удаляет папку по указанному пути. В функции должен быть реализован блок try/except для перехвата возможных исключений. Если удалить папку не удалось, выведите на экран сообщение с информацией о возникшем исключении. Вызовите эту функцию для несуществующей папки.
Работа с файлами 525
Послесловие
Если вы дочитали эту книгу до конца, значит, вы прошли важную часть пути изучения Python. За этим стоит немало времени, усилий и терпения, и эти навыки обязательно пригодятся вам в дальнейшем, независимо от тою. какие технологии вы будете изучать.
А сели вы остановились на каком-то разделе или возвращаетесь к книге время от времени, то это гоже совершенно нормально. В обучении нет единственно правильного маршрута и темпа.
Программирование - это навык, который формируется постепенно и требует времени. Материала этой книги более чем достаточно, чтобы уверенно справляться с задачами по программированию на экзаменах и понимать, как работает язык Python. Она даёт прочную основу, без которой дальнейшее обучение просто невозможно. В то же время программирование в реальной практике выходит за рамки учебных задач. Работа над большими проектами, использование сложных библиотек и инструментов требуют дальнейшею обучения, практики и постепенного накопления опыта. И это нормально: в этой сфере учёба продолжается на протяжении всей жизни.
Самое важное - вы начали. Вы пробовали писать код, разбираться в задачах и сталкивались с ошибками. Каждый такой шаг, независимо от масштаба, делает вас увереннее и опытнее.
Спасибо, что ступили на путь изучения Python вместе с этой книгой. В любом случае вы проделали важную работу, и это заслуживает уважения. Желаю вам успехов, вдохновения и удовольствия от программирования. Пусть полученные знания станут надёжной опорой для сдачи экзаменов, дальнейшего обучения, работы и реализации собственных идей.
С уважением, Хаустова Ирина
Ваш репетитор
Послесловие 526
Ответы и решения
Глава 1. Введение в Python
1.1 Языки программирования
1. Язык, предназначенный для записи программ.
2. Выводит на экран строку "Привет, мир!".
3.0 и 1,
4. По степени их близости к машинному коду.
5. Программу, написанную на компилируемом языке, нельзя запустить без предварительного перевода всего кода программы в машинный код (компиляции), в то время как код программы, написанной на интерпретируемом языке, выполняется построчно специальной программой (интерпретатором) и её запуск не требует предварительной подготовки.
1.2	О языке Python
1.	Гвидо ван Россум.
2.	Python 3. Вторая версия Python больше не поддерживается
3.	Библиотеки - это наборы готовых функций для решения конкретных задач, а фреймворки - это более крупные и сложные инструменты, которые задают структуру программы.
4.	Python применяется в веб-разработке, анализе данных, машинном обучении, автоматизации, создании программ с графическим интерфейсом и многих других областях.
5.	Преимущества Python заключаются в простом синтаксисе, широкой области применения, кроссплатформенности, множестве готовых инструментов и активном сообществе. Недостатки заключаются в более низкой производительности и высоком потреблении оперативной памяти.
1.3	Установка Python и первая про1рамма
1.	Интерпретатор.
2.	Интерактивный режим.
з.	ру.
4.	Запуск кода, написанного в отдельных файлах.
От веты и решения 527
1.4	Редактор кода Visual Studio Code
1.	Кроссплатформенный редактор кода, позволяющий писать, запускать и тестировать код на разных языках протраммирования. Функционал VS Code может дополняться с помощью большого количества расширений.
2.	Нажатием кнопки в виде треугольника слева, либо же через сочетание клавиш Ctrl+F5.
3.	Отображение сообщений об ошибках в коде.
1.5	Рекомендации по оформлению кода
]. Группа инструкций, которые логически связаны и выполняются как единое целое.
2.	4 пробела.
3.	Каждую инструкцию следует помещать на новую строку.
4.	Для интерпретатора нет разницы между одинарными и двойными кавычками, поэтому этот вопрос остаётся на усмотрение программиста. Однако рекомендуется придерживаться одного стиля кавычек в проекте.
5.	Это приведёт к ошибке NameError, так как Python - регистрозависимый язык, и слова, которые отличаются хотя бы одной заглавной или строчной буквой, считаются разными.
1.6 Комментарии
1.	#.
2.	Для пояснений или временного отключения кода.
3.	Можно начинать каждую строку с символа #, или заключить многострочный комментарий в тройные кавычки.
4.	Решение:
# Винегрет
5.	Решение:
"""Матросская шапка, Веревка в руке, Тяну я кораблик По быстрой реке, И скачут лягушки За мной по пятам И просят меня: - Прокати, капитан!
Ответы и решения 528
Глава 2. Основы Python
2.1	Переменные
1.	Именованная ссылка на объект в памяти компьютера.
2.	Решение: password = 'qwerty"
3.	С помощью каскадного присваивания: moviel = movie2 = movie3 = '300 спартанцев"
4.	Решение:
Г руши
5.	Да, так как они созданы с помощью каскадного присваивания и ссылаются на один объект в памяти компьютера.
6.	Решение:
group] = "Ромашки" truth = "Истина в еине" user = "flower" custom_settings = Дополнительные настройки" school_database = "База данных школы"
2.2	Тины данных
1.	Сильная типизация не позволяет смешивать разные типы данных в выражениях без явного преобразования, в то время как слабая типизация автоматически преобразует типы для выполнения выражений.
2.	Интерпретатор определяет тип переменной во время выполнения кода, поэтому одной переменной можно присваивать значения разных типов
3.	Решение:
author: str = "Сьюзен Коллинз"
book_titles: list = ['Голодные игры", "The Hunger Games"]
year: int = 2010
4.	Да. так как будет создан новый объект в памяти.
5.	Решение:
email = "admingrnail.ru" # Строка
print(type(email))
#	Вывод: ccLass 'str‘>
snow = False # Логический тип
print(type(snow))
#	Вывод: <cLass ‘booL '>
Ответы и решения 529
clothes = ( Брюки", "Рубашка", "Галстук") # Кортеж print(type(clothes))
#	Вывод: <cLass 'tupLe’>
points = {"х", "у1, "z"} # Множество
print(type(points))
#	вывод: <cLass 'set‘>
height = 12.5 # Число с плавающей точкой
print(type(height))
#	Вывод: <cLass ‘ float‘>
meal = [ Завтрак", "Обед", "Ужин"] # Список
print(type(meal))
#	Вывод: ccLass ‘List‘>
temperature = {"01.01.2024": -25, "02.01.2024": -26} # Словарь print(type(temperature)) # Вывод: <class ‘dict'>
2.3	Числа
1.	Строки.
2.	Решение:
12
9
3.	Числа с плавающей точкой (float).
4.	Решение:
х = 1.2е+10
у = 7.1е-7
5.	Преобразует целое число или строку в число с плавающей точкой.
2.4	Арифметические операции
1	Сначала выполняется возведение в степень (**), затем - умножение (*) и деление (/, // и %), и в последнюю очередь - сложение (+) и вычитание (-). При этом операторы, имеющие одинаковый приоритет, выполняются слева направо.
2.	От результата деления отбрасывается дробная часть, поэтому результатом всегда является целое число.
3.	Решение:
2
2
2.5 -20.0
25
11
Ответы и решения 530
4.	Решение
1.0
8
7
-12
1.0
2.5	Математические функции
1.	Решение:
15.5 100 0
2.	Решение:
print(round(0.15865, 3))
#	Вывод: 0.159 print(round(32.23124, 3))
#	Вывод: 32.231
print(round(-8.87231))
#	Вывод: -9
print(round(14.5))
#	Вывод: 14
3.	В каждом случае функция round () округляет число до разного количества знаков после запятой:
3.14 3.1416
3.0
4.	В Python функция round() использует банковское округление (округление до ближайшего чётного), поэтому round(2.5) округляет число до 2, а round(3.5) - до 4.
5.	Решение:
stepi =2.75
step2 = 3
final_result = 27
6.	Функция pow(5, 3) просто вычисляет степень, a pow(5, 3, 2) дополнительно берёт остаток от деления:
print(pow(5, 3)) # Эквивалентно 5 ** 3
#	Вывод: 125
print(pow(5, 3, 2)) # Эквивалентно 5 ** 3 % 2
#	Вывод: 1
Ответы и решения 531
2.6	Основы работы со строками
1.	Они сохраняют перенос строки.
2.	Используем функцию str():
str_number = str(23)
3.	Вместо целочисленной переменной price при конкатенации строк следует использовать строковую переменную str_price:
price = 17
str_price = str(price) print(str_price + " долларов ) # Вывод: 17 долларов
4.	Решение:
print( Ура" * 7)
5.	Решение:
Сумма чисел 3 и 5 равна 8
6.	Решение:
е = 2.7182818284"
print(f Число Эйлера приблизительно равно {e:.2f}")
# Вывод: Число Эйлера приблизительно равно 2.718
2.7	Экранирование и кодирование символов
1.	Переводит каретку на новую строку.
2.	Решение:
print( Величина\1Единица\пСила света\Ткандела\пСила тока\1ампер")
3.	Универсальный стандарт для кодирования большинства существующих символов.
4.	Решение:
print( C:\Program files\\browser\\test")
print( A: Wnecessary filesWtimetable' )
print( D:\\reading\\bulgakov )
5.	Функция ord() возвращает порядковый номер символа в Юникоде, а функция chr(). наоборот, возвращает символ по его порядковому номеру.
2.8	Ввод и вывод данных
]. Решение:
"Хлеб, молоко, колбаса и что-то ещё, - вспоминал в магазине Иван, - наверное, ещё конфеты".
Ответы и решения 532
2.	Решение:
name = "Пьер"
last_name = "Безухов"
print(f Добрый вечер, дорогой {name} {last_name}! )
3.	Решение:
nl = int(input("Введите первое число: ")) n2 = int(input( Введите второе число: )) print(f {nl} + {n2} = {nl + n2}")
4.	Строка.
5.	Решение:
movie = input('> едите название вашег^ любимого фильма: ) princ(f Мне тоже нравится фильм \"{movie}\"! )
2.9 Логические выражения и операторы
1 п и message
2.	Решение:
False
True
True Т rue Т rue
3.	Сначала выполняется логическое отрицание not, затем логическое умножение and и в конце логическое сложение or.
4.	Решение:
True False False Т rue Т rue
5.	Решение:
age = int(input())
print(age >= 200u and age <= 2004)
2.10 Ветвление
1.	Решение:
day = int(input())
match day:
case 1:
print('Понедельник )
case 2:
print( Вторник")
Ответы и решения 533
case 3:
print(-'Среда") case 4:
print( 'Четверг )
case 5:
print( ’Пятница”)
case 6:
print('Суббота")
case 7:
print("BocKpeceHbe' )
case
print('Такого дня недели не существует' )
2.	Решение:
nl = int(input())
n2 = int(input())
operator = input()
if operator ==
print(f {nl} + {n2} = {nl + n2}“)
elif operator ==
print(f"{nl} {n2} = {nl - n2} ) elif operator ==
print(f {nl} * {n2} = {nl * n2}“)
elif operator ==
print(f {nl} I {n2j = {nl / n2}") else:
print( 'Неизвестный оператор")
3.	Решение:
nl = int(input())
n2 = int(input())
if nl >0 and n2 > 0:
if nl > n2:
print(nl)
else:
print(n2) elif nl < 0 and n2 < 0: if nl < n2:
print(nl)
else:
print(n2)
else:
print(f"{nl}, {n2}‘)
4.	Решение:
x = int(input()) у = int(input())
Ответы и решения 534
if 61 + x == 98 - у:
print( 'Да ') else:
print('Нет')
5.	Решение:
n = int(input()) digxt_l = n // 100 digit_3 = n % 10 if digit_l == digit_3:
print("fla )
else:
printC'HeT")
2.11	Циклы
]. Решение:
for i in range(ll):
print(i, end= )
print() # Для переводи на новую строку for i in range(10, 27, 4):
print(i, end=" )
print() # Для перевода на новую строку for i in range(20, 14, -1):
print(i, end=" ")
2.	Решение:
n = int(input()) divider = n
for i in range(2, n + 1):
if n % i == 0: divider = i break print(divider)
3.	Решение:
n = 0
summa = 0
while n != "Стоп" and n 1= "Хватит”:
summa += int(n)
n = input() print(summa)
4.	Решение:
n = int(input()) even_n = 0 while n % 2 == 0:
even_n = n n = int(input())
Ответы и решения 535
print(even_n)
5.	Решение:
n = int(input())
for i in range(l, n + 1, 2): if 11 <= i <= 21:
continue print(i, end= )
6.	Решение:
n = int(input())
s = input()
for i in range(n):
for j in range(i, -1, -1): prlnt(s, end=" ”)
print() # Для перевода на новую строку
2.12	Ошибки и исключения
1.	Пропущено двоеточие в условии if age > 38 и пропущена закрывающая кавычка в строке "Ты совершеннолетний и можешь голосовать":
age = int(input('Сколько тебе лет? ))
if age >= 18:
print( "Ты совершеннолетний и можешь голос-,вать )
else:
print('Tbi ещё несовершеннолетний, тебе нужно подождать )
2.	Исключение вызвал код на строке 5. Причиной является деление на ноль,
3.	Решение:
try:
user_input = input( "Пожалуйста, введите целое числе: )
number = int(user_input)
print(f Вы ввели число {number}' )
except ValueError:
print( He удалось преобразовать e числе )
4.	Блок else будет выполнен только в том случае, если код внутри блока try выполнился успешно и не вызвал исключений.
5.	Решение:
try:
age = int(input("Пожалуйста, введите ваш возраст: "))
except ValueError:
printC'F-зеденное значение должно быть целым числом") else:
print("доступ разрешен." if age >= 16 else "Доступ запрещён") finally:
Ответы и решения 536
print('Проверка возраста завершена')
Глава 3* Коллекции
3.1	Введение в коллекции
1	Кортежи.
2.	В множестве будет 4 элемента, так как повторяющиеся значения будут проигнорированы.
3.	Строки, списки, кортежи, словари.
4.	Множества Они позволяют хранить уникальные и неупорядоченные элементы.
5.	Будет вызвано исключение ТуреЕггог, так как строки являются неизменяемой коллекцией.
3.2	Индекеииия и срезы
1.	Третий элемент списка находится по положительному индексу 2, и по отрицательному индексу -3:
numbers = [10, 20, 30, 40, 50]
print(numbers[2 ])
#	Вывод: Зв
print(numbers[- 3])
#	Вывод: Зв
2.	Решение:
fruits = (["Яблоко", "Банан"], ["Апельсин*, "Киви"])
print(fruits[0][1 ])
# Вывод: Банан
3.	Решение:
colors = [ Красный", "'оранжевый", "Жёлтый', "Зелёный , Голубой", ‘Синий , "Фиолет овый' ]
print(colors[2:])
#	Вывод: ['Жёлтый', 'Зелёный', 'Голубой', 'Синий', 'Фиолетовый']
print(colors[1:4])
#	Вывод: ['Оранжевый', 'Жёлтый', ‘Зелёный']
print(colors[2: -2])
#	Вывод: ['Жёлтый', 'Зеленый', 'Голубой']
orint(colors[l::2])
#	Вывод: ['Оранжевый', 'Зелёный', 'Синий']
print(colors[-2:-6: -1])
#	Вывод: ['Синий', 'Голубой', 'Зелёный', 'Жёлтый']
Ответы и решения 537
4.	Решение:
sentence = ’Весна, весна на улице, весенние деньки!" new_sentence = sentenced::3]
print(new_sentence)
# Вывод: с,еаалевеидь!
5.	Решение:
user = 'радар"
print(user[::-1])
# Вывод: радар
3.3	Перебор элементов коллекций
]. Решение:
unique_numbers = {5, 2, 8, 2, 9, 5}
for number in unique_numbers: prinr(number, end=" )
# Вывод: 8 9 2 5
2.	Решение:
city_population = { Москна": 13149803, "Санкт-Петербург": 5597763, 'Владивосток”: 622782}
for city, population in city population.items(): print(f"{city} - население -(population} )
#	Вывод- Москва - население 13149803
#	Вывод: Санкт-Петербург - население 5597763
#	Вывод- Владивосток - население 622782
3.	Решение:
n = int(input( Введите целое число п: "))
for i in range(2, n + 1):
if i % 2 == C: print(i, end=" )
4.	Решение:
input_string = input( Введите строку: ")
1 = 0
while i < len(input_string): print(input_string[i], end= ") i += 1
3.4	Общие функции и операторы коллекций
1.	Решение:
numbers = [15, 8, 23, 8, 42, 10]
size = len(numbers)
sum_numbers = sum(numbers)
print(f Размер списка: {size}. Сумма элементов: {sum_numbers}')
Ответы и решения 538
# Вывод: Размер списка: 6. Сумма элементов: 106
2.	Решение:
grades = [3, 4, 5, 3, 4, None, 5, 5, None]
allgraded = all(grade is not None for grade in grades)
print(all_graded)
# Вывод: FaLse
3.	Решение:
fruits = ( Яблоко , Банан", "апельсин", ’киви")
min_fruit = min(fruits)
max_fruit = max(fruits)
print(f‘ Наименьшее слово: {min_fruit}. Наибольшее слово: {max_fruit) )
# Вывод: Наименьшее слово: Ьанан. Наибольшее слово: киви
4.	Решение:
prices = { Авокадо": 20О, "Киви": 150, "Голубика": 175}
is_avocado = ( Авокадо , 125) in prices.items()
print(is_avocado)
# вывод: FaLse
5.	Распаковка - это извлечение элементов из итерируемого объекта (например, списка, кортежа, множества), а упаковка - это объединение элементов в один объект. Для распаковки словаря используется оператор **, а для списка - оператор *.
6.	Решение:
n = int(input( Введите числе фруктов: "))
fruits_list = [input('Введите фрукт: '") for _ in range(n)]
print( Вы любите яблоки" if "Яблоко" in fruits_list else Вы не любите яблоки")
3.5	Методы преобразования и выравнивания строк
1.	Строки в Python являются неизменяемым липом данных. Поэтому методы, которые преобразуют строку, создают и возвращают новую измененную копию, оставляя исходную строку без изменений.
2.	Решение:
s = input( Введите строку для преобразования t список: )
sep = input("Введите разделитель: ")
1st = text.split(separator)
print(1st)
3.	Решение:
message = "Привет, друг!" print(message.center(20, )) # Вывод: ---Приветл друг----
Ответы и решения 539
4.	Нужно использовать метод str.strip():
order = " Заказ №29в HW" print(order.strip( №')) # Вывод: Заказ NV98
Для удаления лишних символов только в начале строки можно воспользоваться методом str.lstripQ:
order = Заказ N’298 NW" print(order.lstrip( ' №")) # Вывод: Заказ №298
5.	Решение:
answer = input("Хотите продолжить? (да/нет): ”).lower()
if answer == "да":
print( Продолжение работы') elif answer == "нет":
print( Завершение работы )
else:
print( Команда не распознана )
З.б	Mei оды поиска и проверки прок
1.	Решение:
text = "Привет, мир! Как дела, мир?"
index = text.find( мир", 7) print(index) # Вывод: 18
2.	Для поиска последнего вхождения подстроки используются методы str. rindex() и str.rfind(). Если подс!рока не найдена, метод str. rindex() вызывает исключение VaiueError, а метод str.rfind() возвращает -1.
3.	Решение:
text = "Ура!!! Получилось!" count = text.count("!') print(count) # Вывод: 4
4.	Оба метода вернут False, так как строка содержит как заглавные, так и строчные буквы-
vk = "Вконтакте" print(vk.islower()) # Вывод: FaLse p^int(vk.isupper()) # Вывод: FaLse
Ответы и решения 540
5.	Решение:
postal_code = input( :ведите почтовый индекс: )
if posta]_code.isdigit():
print( введён корректный почтовый индекс")
else:
print( 'Почтовый индекс должен состоять только из цифр )
6.	Решение:
file_name = 1приТ("Введите имя файла: ) if file_name.endswith( .txt"):
print("9TO текстовый файл ) else:
print('Это не текстовый файл")
3.7	Списки и кортежи
1.	Списки являются изменяемыми, а кортежи - неизменяемыми.
2.	Решение:
s = input( 'ведите строку: ) print(list(s))
3.	Запятая создаёт кортеж. Без запятой Python интерпретирует выражение как обычное значение (например, строку, если элемент является строкой).
4.	Решение:
1st = ["Раз', "Два"]
1st *= 3 print(lst) # Вывод: ["Раз", "Два", "Раз", “Два", "Раз", "Два"]
5.	Решение:
1st = [1, з, 5, 7, 5] p-'int (1st.index(5)) # Вывод: 2
6.	Решение:
subjects = ( Физика , "Физика", "Математика , ’Русский язык", "История") print(subjects.count( Физика')) # Вывод: 2
3.8	Методы списков
1.	Список добавляется целиком как отдельный вложенный элемент.
2.	Решение:
statues = [ Статуя Единства' , "Статуя Свободы1’]
statues.insert( Рабочий и колхозница", 1) print(statues)
# Вывод: ['Статуя Единства', ’РаЬочий и колхозница', Статуя Свободы']
От веты и решения 541
3.	Для удаления элементов из списка используются методы list.remove() и list.pop(). Метод list.remove() удаляет первое вхождение элемента по значению. а метод list. pop() удаляет элемент по индексу и возвращает удаленный элемент. Если индекс не указан, удаляется и возвращается последний элемент.
4.	Решение:
numbers = [1, 2, 3, 4, 5]
deleted_number = numbers.рор(2)
print(f"Удалён элемент: {deleted_number} )
# Вывод: Удалён элемент: 3
5.Функция sorted() не изменяет исходный список, а возвращает новый отсортированный список. Метод list.sort(), наоборот, сортирует (изменяет) исходный список и возвращает None.
6. Решение:
Istl = input( 'Введите первую строку: ).split()
lst2 = input( Ьведите вторую строку: ).split()
1st = Istl 4- lst2
print(sorted(1st, key=len))
3.9	Множества
1	Множества не упорядочены и содержат только уникальные значения.
2.	Решение:
duplicates = [1, 2, 2, 3, 4, 4, 5, 5, 5J
1st = list(set(duplicates))
print(lst)
# Вывод: fl, 2, 3, 4, 5]
3.	Решение:
empty_set = set()
empty_set.add(10)
empty_set.add(20)
print(empty_set)
# Вывод: {10, 20}
4.	Решение:
tasks = {'Работа', "Учеба', "Сон", "Спорт"}
completed_task = tasks. р-.>р()
print(completed_task)
# Вывод: Спорт
5.	Решение:
languages = ['Python', "TavaScript", "C++', "Python"]
frozen_languages = frozenset(languages)
print(frozen_languages)
Ответы и решения 542
# Вывод: frozenset({ ‘JavaScript', 'C++', ‘Python'})
Замороженное множество неизменяемо, поэтому в него нельзя добавить новый элемент.
3.10	Операции над множествами
1.	Результат объединения, пересечения и симметричной разности не зависит от порядка множеств, так как они включают или исключают все общие элементы. Однако при разности изменение порядка множеств изменяет результат операции.
2.	Разность set - set2 является односторонней и убирает из множества setl все элементы, присутствующие в множестве set2 Симметричная разность setl Л set2 является двусторонней и убирает все элементы, принадлежащие обоим множествам (их пересечение).
3.	Решение:
set_a = {1, 2, 3, 4, 5} set_b = {4, 5, 6, 7, 8} print(set_a | set_b)
#	Вывод: {1, 2} 3J 4, 5, 6, 7, 8} print(set_a & set_b)
#	Вывод: {4, S} print(set_a - set_b) # Вывод: {1, 2, 3} print(set_a Л set_b)
#	Вывод: {1, 2j 3j 6j 7, 8}
4.	Решение:
team_a = {'Иван", "Дмитрий1, "Елена", "Ольга"} team_b = {"Ольга", Пётр", Дмитрий", "Мария"} team = teama & team_b print(team)
# Вывод: {'Дмитрий', 'Ольга'}
5.	Решение:
colorsl = {"Красный", "Синий", "Желтый"} colors2 = {"Синий", "Желтый"} print(colorsl >= coiors2) # Вывод: True print(colorsl <= colors2) # Вывод- FaLse
3.11	Сливари
I.	Пары «ключ-значение» могут быть переданы как именованные
Ответы и решения 543
аргументы ключ=значение или последовательность кортежей с парами (ключ, значение).
2.	Решение: food = {
"Яблоки красные": "78 руб/кг' ,
"Картофель свежий": "83 руб/кг", "Помидоры красные": "120 руб/кг" } print(food.get("Яблоки красные", "Нет данных )) # Вывод: 78 руб/кг print(food.get("Груши желтые' , "Нет данных")) # Вывод: Нет данных
3.	Решение:
capitals = {
"Воло! лцская область": "Вологда , "Республика Татарстан": "Казань , "Республика Саха": Якутск", "Германия": "Берлин" } capitalsf Вологодская область"] = "Череповец" del capitals[ Германия"] print(capitals)
# Вывод: {'Вологодская область': 'Череповец', 'Республика Татарстан': 'Казань', ‘Республика Саха': 'Якутск'}
4.	Решение:
team = [(Иван", 25), ("Мария", 30), ("Петр , 28)] team_dict = dict(team ) print(team_dict ) # Вывод: {'Иоан': 25, Мария': ЗВ, 'Петр': 28}
5.	Решение:
numbers = list(range(l, 6)) english_words = [One", "Two", "Three", "Four", "Five'] numbers_dict = dict(zip(numbers, english_words)) print(numbers_dict)
# Вывод: {1: 'One', 2: ‘Two1, 3: ’Three", 4: 'Four', 5: 'Five'}
3.12	Генераторы коллекций
1.	Решение:
brands = [’Apple", "Samsung", "Xiaomi , "LG"]
longbrands = [brand_name for brand_name in brands if len(brand_name) >= 6] print(long_brands)
# Вывод: ['Samsung', 'Xiaomi']
Ответы и решения 544
2.	Решение:
numbers = [1, 2, 2, 3, 4, 4, 5]
even_numbers = {n for n in numbers if n % 2 == 6} print(even_numbers)
# Вывод: {2j 4}
3.	Решение:
prices = {
"Роза красная": 180.5,
"Хризантема жёптая": 355.5,
'Пион розовый": 325.5
}
increased_prices = {item: price * 1.1 for item, price in prices.items()} print(increased_prices)
# Вывод: {‘Роза красная': 198.55, 'Хризантема жёлтая': 391.05, 'Пион розовый': 358.05}
4.	Решение:
zero_matrix = [[0 for _ in range(3)] for _ in range(3)]
for row in zero_matrix:
print(row)
#	Вывод: [0, 0,0]
#	Вывод: [0, 0, 0/
#	Вывод: [0, 0, 0]
5.	Решение:
n = int(input())
numbers = [x ** 2 for x in range(l, n + 1)]
print(numbers)
Глава 4* Функции
4.1 Создание функции
]. Параметры - это переменные, которые функция ожидает получить, а аргументы - это конкретные значения параметров, которые мы передаём функции.
2.	Решение:
def greet() -> None:
print("Добро пожаловать в мир функций!")
greet()
# Вывод: Добро пожаловать в мир функций!
3.	Решение:
def filter_positive(numbers: list[int]) -> list[int]:
Ответы и решения 545
positive_numbers = [num for num in numbers if num > 0] return positive_numbers
#	Вызов функции для заданного списка data = [1, -12, 31, -4, -5, 0, 24] print(filter_positive(data))
#	Вывод: fl, 31, 24]
4.	Решение:
def get_unique(numbers: list[int]) -> list[int]:
#	Преобразуем в множество, а потом снова в список result = list(set(numbers)) return result
numbers = [100, 100, 20d, 1, 100, -2, -2] print(get_unique(numbers))
#	Вывод [200, 1, 100, -2]
5.	Решение:
def get_initials(full_name: str) -> str:
# Извлекаем первую букву каждого слова initials_list = [пате[й].upper() for name in full_name.split()] # Объединяем список в строку result = " .join(initials_list) return result
name_string = ‘Айвазовский Иван Константинович'" print(get_initials(name_string))
# Вывод: АИК
4.2	Параметры и аргументы функций
1.	Позиционные ар]уменгы передаются в функцию в порядке их объявления, а именованные ар1ументы передаются по имени. При этом позиционные аргументы передаются до именованных.
2.	Решение:
def calculate_total(price: float, quantity: int, discount: float = u) -> float: total_cost = price * quantity
final_cost = total_cost * (1 - discount / 10B) return final_cost
print(calculate_total(85, 2, discount=10))
# Вывод: 153.0
Ответы и решения 546
3.	Значения по умолчанию создаются в момент определения функции, поэтому если вызвать эту функцию несколько раз и изменить внутри неё какой-то элемент, то все последующие вызовы будут использовать и изменять ту же самую. уже изменённую, копию объекта.
4.	Параметры *args и **kwargs позволяют передать в функцию произвольное количество аргументов. *args собирает позиционные аргументы в виде кортежа, a **kwargs собирает именованные аргументы в виде словаря. Параметры *args передаются до параметров **kwargs.
5.	Решение:
def show_profile(username: str, **details) -> None:
#	Оператор * распаковывает словарь detaiLs print(username, ’details.values(), sep=", )
show_profile(username= karen , occupation= Журналист , age="27")
#	Вывод: karen, Журналист, 27
4.3	Области видимости переменных
1.	Область видимости переменной - это часть программы, в которой переменная доступа для использования. 11ространства имён - это система, сопоставляющая каждому имени соответствующий объект.
2.	Правило LEGB определяет, где Python ищет имя объекта. Поиск начинается с локальной (L) области (внутри текущей функции). Если имя не найдено, поиск переходит в нелокальную (Е) область (для переменных во внешней функции). Далее идет глобальная (G) область (уровень модуля), и, наконец, встроенная (В) область, содержащая стандартные функции Python.
3.	Ключевое слово global позволяет функции изменять значение глобальной переменной, а слово nonlocal позволяет внутренней функции изменять значение переменной внешней функции (нелокальной переменной).
4.	Будет выведена строка "Император России: Николай Романов", так как функция change_name() не изменяет значение глобальной переменной name, а создаёт новую локальную переменную name.
5.	Решение:
total_sum = 0
def add_to_total(number: int) -> None:
global total_sum
total_sum += number
Ответы и решения 547
add_to_total(1(') add_to_total(5) print(total_sum)
# Вывод: 15
4.4	Рекурсивные функции
1.	Рекурсия - это способ решения задачи, при котором функция вызывает сама себя. Она состоит из базового случая - условия, при котором функция прекращает вызывать саму себя, и рекурсивного случая - части, где функция вызывает саму себя, но с измененными аргументами, которые приближают ее к базо
вому случаю.
2.	Базовый случай необходим для выхода из функции, иначе функция будет бесконечно вызывать сама себя.
3.	Когда размер стека превышает максимальную глубину рекурсии (макси
мальное количество вложенных вызовов рекурсивной функции).
4.	Будет выведено число 8, гак как функция mystery (п) вычисляет 2П: о mystery(3) возвращает 2 * mystery(2).
о mystery(2) возвращает 2 * mystery(l).
о mystery(l) возвращает 2 * mystery(O).
о mystery(0) возвращает 1 (базовый случай).
То есть функция вычисляет выражение 2 * (2 * (2 * 1)).
5.	Решение:
def power(base: float, exponent: int) -> float:
#	Базовый случай: любое число в степени в равно 7
if exponent == 0: return 1
#	Рекурсивный шаг: base ** exponent = base * base ** (exponent - 1) else:
return base * power(base, exponent - 1)
print(power(2, 4))
4.5	Функции высшего порядка
1.	Это функции, которые принимает одну или несколько других функций в качестве аргументов и/или возвращает другую функцию в качестве своего результата. К ним относятся встроенные функции sorted(), map() и filter().
Ответы и решения 548
2.	Решение:
def reduce_by_ten_percent(x: int | float) -> float: return x * 0.9
result = list(map(reduce_by_ten_percent, [105, 1И2, 24, 46, 5и])) print(result)
# Вывод: [94.5, 91.8, 21.6, 41.4, 45.0]
3.	Будет выведен список [1, 3, 5, 7], так как функция filter() отбирает из списка numbers все нечетные числа.
4.	Решение:
def normalize(r.ame: str) -> str: return name.capitalize()
old_names = [ 'савельич", "ГРИНЁВ1, "ШВАЕРин ]
new_names = list(map(normalize, old_names)) print(new_names)
# Вывод: [‘Савельич’, 'Гринёв', ‘Швабрин']
5.	Решение:
def is_short_wora(word: str) -> bool: return len(word) <= 4
words = ["Киви", "Пляж", "Физика", "Кот", "Программирование"] short_words_list = list(filter(is_short_word, words)) print(short_words_list)
# Вывод: ['Киви', 'Пляж', 'Кот']
4.6	Анонимные фу нкции
1.	Анонимная функция - это небольшая однострочная функция без имени.
Она создаётся с помощью ключевого слова lambda
2.	Будет выведено число 15, так как анонимная функция принимает два числа и возвращает результат их сложения.
3.	Решение:
product_lambda = lambda х, у, z: х * у * z result = product lambcidp, 5, 4) print(result)
# Вывод: 40
4.	Решение:
students = [
("Менделеев Иван", 4.5),
Ответы и решения 549
("Кюри Мария , 5.0)j
("Кюри Пьер , 3.8)
]
sortedstudents = sorted(students, key=lamdda student: student[l], re-verse=True
print(sorted_students)
# Вывод: [('Кюри Мария', 5.0), ('Менделеев Иван', 4.5), ('Кюри Пьер', 3.8)]
5.	Решение:
numbers = [1, 2, 3, 4, 5]
result = list(map(]ambda х: х + 10, numbers)) print(result)
# Вывод: [11, 12, 13, 14, 15]
6.	Решение:
zoo = ["слон1, "жираф1, "крокодил1, "зебра", "ксала"] result = list(filter(lambda word: “к" in word, zoo)) print(result)
# Вывод: ['крокодил ', 'коала']
4.7	Итераторы и генераторы
1.	Итерируемый объект - это объект, элементы которого можно перебрать, например, в цикле for. Итератор - это механизм, который обеспечивает этот перебор.
2.	Функция iter () преобразует итерируемый объект в итератор, а функция next() возвращает следующий элемент итератора. Если функция next() применяется к пустому итератору, то вызывается исключение Stopiteration.
3.	Решение:
def simple_gen():
yield "Ты!" yield "Молодец!"
gen = simpie_gen()
print(next(gen))
print(next(gen))
ffprint(next(gen))
Здесь функция next() вызывается три раза, хотя генератор определен только для двух значений, что приводит к исключению Stopiteration на третьем вызове, поэтому его следует убрать.
4.	Решение:
def squares_generator(n: int):
Ответы и решения 550
for i in range(n + 1): yield i * i
squares = squares_generator(5) for square in squares:
print(square, end=" ')
# Вывод: 0 1 4 9 16 25
5.	Решение:
def get_plus(text: str):
while True:
text += "+" yield text
initial_text = "Отличии' generator = get_plus(initial_text) for i in range(3):
print(next(generator) )
#	Вывод: Отлично+
#	Вывод- Отлично++
#	Вывод: Отлично+++
4.8	Замыкание и декораторы
1.	Замыкание заключается в том, что вложенная функция, имеет доступ к переменным внешней функции даже после завершения выполнения этой функции. Оно происходит, если внутренняя функция использует переменные внешней функции и внешняя функция возвращает внутреннюю функцию.
2.	Декоратор - это функция высшего порядка, которая изменяет поведение другой функции, не изменяя её исходный код. Он представляет собой функцию, которая содержит другую функцию. При этом внешняя функция принимает декорируемую функцию и возвращает вложенную функцию, которая принимает аргументы декорируемой функции и вызывает её.
3.	Решение:
def create_counter() -> "function":
count = 0
def incrementO -> int: nonlocal count count += 1 return count return increment
Ответы и решения 551
counter = create_counter() print(counter())
#	Вывод: 1 print(counter())
#	Вывод: 2
print (counterO)
#	Вывод: 3
Здесь пропущено ключевое слово nonlocal перед переменной count, а также переменной counter следует присвоить внешнюю функцию create_coun-ter().
4.	Решение:
def print_start_end(func: "function") -> "function": def wrapper(*args, **kwargs):
print("Ha4a,no выполнения ) result = func(*args, **kwargs) print('Конец выполнения") return result
return wrapper
(Sp r i n t_s t a r t_e n d
def process_data(data: str) -> Kone:
print(f"Обработка данных: {data}")
process_data("Секретные материалы )
#	Вывод: Начало выполнения
#	Вывод: Обработка данных: Секретные материалы
#	Вывод: конец выполнения
5.	Решение:
def repeat(n: int):
def decorator(func: "function") -> "function": def wrapper(*args, **kwargs) -> None:
for _ in range(n): func(*args, *+kwargs) return wrapper return decorator
^repeat(n=3)
def say_a_poem() -> None:
print( Однажды, в студёную зимнюю пору\п"
"Я из лесу вышел; был сильный мороз.' )
say_a_poem()
Ответы и решения 552
#	Вывод: Однажды, в студёную зимнюю пору
#	Вывод: Я из лесу вышел; был сильный мороз.
#	Вывод: Однажды, в студеную зимнюю пору
#	вывод: Я из лесу вышел; был сильный мороз.
#	Вывод: Однажды, в студёную зимнюю пору
#	Вывод: Я из лесу вышел; был сильный мороз.
Глава 5. Объектно-ориентированное программирование
5.1	Классы и объекты
1.	Процедурное программирование организует код в виде функций, а ООП в виде классов и объектов.
2.	Класс - это шаблон, определяющий свойства и поведения объекта, а объект- это сущность, объединяющая данные и функции. Например, есть класс кактусов. который задаёт свойства каждого кактуса: цвет, колючки, размер, и его поведение: сохнуть без воды. Л также есть конкретный и любимый кактус на подоконнике, как объект класса кактусов, который почти без колючек и не сохнет без воды месяц.
3.	Решение:
class Point:
def _init_(self, x, y):
self.x = x self.у = у
point = Point(5, 10) print(point.___d ict_)
# Вывод: {'x‘: 5, 'y‘: 10}
В словаре мы видим атрибуты экземпляра х и у вместе с их значениями.
4.	Решение:
class Book:
def___init___(self, title: str, author: str):
self.title = title
self.author = author
bookl = Book("Война и мир", "Лев Николаевич Толстой") print(bookl.title)
#	Вывод: Война и мир
Ьоок2 = Воок("451 градус по Фаренгейту", "Рэй Брэдбери")
Ответы и решения 553
print(book2.title)
#	Вывод: 451 градус no Фаренгейту
5.	Решение:
class Lamp:
def__init__(self, is_on=False):
self.ison = ison
def toggle_switch(self) -> None: self.is_on = not self.is_or
my_lamp = Lamp()
my_lamp.toggle_switch()
print(f "Включаем: {my_lamp.is_on} ) # Вывод: Включаем: True my_lamp.toggle_switch()
print(f'Выключаем: (my_lamp.is_on} ) # Вывод: Выключаем: FaLse
5.2	Инкапсуляция и ограничение доступа
1.	К защищённым атрибутам можно обращаться напрямую извне класса, а к приватным - нет.
2.	Решение:
class BankAccount:
def _init__(self, owner_name: str, current_balance-Li' 0):
self._owner_name = owner_name self.__current_balance = 0
Защищённые атрибуты начинаются с одного подчеркивания (_), а приватные - с двух (_).
3.	Решение:
class User: def______init__(self, age: int):
self.__age = age
def get_age(self) -> int: return self._____age
user = User(45) current_age = user.get_age() print(current_age)
# Вывод: 45
Ответы и решения 554
4.	Решение:
class User:
def __init__(self, age: int):
self.__age = age
def get_age(self) -> int: return self.______age
def set_age(self, new_age: int) -> None:
if 0 <= new_age <= 100:
self._age = new_age
else:
print(f"Возраст должен быть в диапазоне от 0 до 106' )
#	Создание объекта и изменение атрибута user = user(45) user.set_age(18) user.set_age(-21)
#	Вывод: Возраст допжен быть в диапазоне от в до 100 print(user,get_age())
#	Вывод: 28
5.	Решение:
class User: def _____init_(self, age: int):
self.age = age
^property def age(self) -> int: return self._____age
@age.setter
def age(self, new_age: int) -> None: if e <= new_age <= 100: self._________age = new_age
else: print("Возраст должен быть в диапазоне от 0 до 100 )
# Создание объекта user_2 - User(30) user_2.age = 13 print(user_2.age) # Вывод: 13
5.3	Наследование и полиморфизм
1.	Проблема ромба может возникнуть при множественном наследовании,
Ответы и решения 555
если два родительских класса наследуют от общего предка. Она решается благодаря порядку разрешения методов, согласно которому поиск идёт от самого специфического класса (дочернего) к самому общему (родительскому), а также сохранится порядок, в котором были указаны родительские классы.
2.	Решение:
class Саг:
def move(self) -> None:
pr'int( Едет по дороге...")
class Bicycle:
def move(self) -> None:
print( Едет по горной трипе...")
class Boat:
def move(self) -> None:
print( Плывет по реке...")
vehicles = [Car(), BicycleC), ^oat()] for- vehicle in vehicles:
vehicle.move()
#	Вывод: Едет no дороге...
#	Вывод: Едет по горной тропе ..
#	Вывод: Плывет по реке...
3.	Решение:
class Vehicle:
# Общий метод для запуска
def start_engine(self) -> None: print(f'Транспорт запущен')
class Car(Vehicle):
def move(self) -> None: рг!пт("Едет по дороге.. )
class ficycle(Vehicle):
def move(self) -> None:
print( Едет по горной тропе... )
class Boat(Vehicle):
def move(self) -> None:
print( Плывет no реке...")
Ответы и решения 556
boat = Boat()
boat.start_engine()
# Вывод: Транспорт запущен
4.	Решение:
class Employee:
def log_wo''k(self) -> None:
print('Сотрудник приступил к работе'1)
class Manager(Employee):
def log_work(self) -> None: # Вызываем метод родителя super().log_work()
#	Добавляем дополнителоную логику print('B 12:00 будет совещание")
manager = Manager() manager.log_work()
#	Вывод: Сотрудник приступил к работе
#	Вывод: В 12:00 будет совещание
5.	Решение:
class Walker:
def walk(self) -> None: print( 'Идет по земле")
class Swimmer:
def swim(seif) -> None: print('Плывет в воде")
class Amphibian(Walker, Swimmer): def_______init__(self, specie: str):
self.specie = specie
frog = Amphibian("Лягушка") frog.walk()
#	Вывод: Идет no земле
f rog. swim()
#	Вывод: Плывет в воде
5.4	Получение данных об объекте
1.	Будет вызван метод____str___(), так как функция print () вызывает метод
Ответы и решения 557
__rerp___() только при отсутствии метода______str___().
2.	Это происходит потому, чю в Python все классы неявно наследуются от базового класса object, который определяет базовую реализацию всех магических методов.
3.	Решение:
class Date: def_______init__(self, day: int, month: int, year: int):
self.day = day self.month = month self.year = year
def __str___(self) -> str:
return f "{seif.day;02d}.{self.month:02d).{self.year)
def __repr__(self) -> str:
return f Ddte(day-{self.day), month={self.month), year={self.year})"
today = 0ate(day=18, month=12, year=2025)
#	Функция print() вызывает метод _str__()
print(today)
#	Вывод: 18.12.2025
#	Функция repr() вызывает метод __repr_()
repr_string = repr(today) print(repr_stri ng)
#	вывод: Date(day-18, month=12i year=2025)
4.	Решение:
class Secretitem: def_______init__(self, name: str, code: str):
self.name = name self.___code = code
def __dir___(self) -> list[str]:
rerurn [''name']
item = Secretltem( Чертеж', "X-777') print(dir(item))
# Вывод: ['name']
5.	Решение:
class Furniture: def_______init__(self, color: str):
self.color = color # Цвет
Ответы и решения 558
class Chair(Furniture):
def ___init__(self, color: str, legs: int = 4):
super().__init__(color)
self.legs = legs # Количество ножек
my_chair = Chair( Синий , 3)
is_furniture = isinstance(my_chair, Furniture)
print(f"Объект 'my_chair‘ - экземпляр класса Furniture: {isfurniture}") # Вывод: Объект 'my_chair' - экземпляр класса Furniture: True
5.5	Атрибуты и методы класса. Статические методы
1.	Нет. так как это не изменяет значение атрибута класса, а создаёт новый атрибут экземпляра класса с этим же именем.
2.	Статические методы представляют собой обычные функции, помещённые вну три класса.
3.	Решение:
class Planet:
planet_count: int = 0 ft Атрибут класса
def _init__(self, name: str):
self.name = name
# При создании нового объекта увеличиваем общий счетчик класса
Planet.planet_cojnt += 1
pl = Planet( ‘Земля")
p2 = Planet( ’Марс”)
рЗ = Planet('Юпитер' )
print(f"Создана планет: {Planet.planet_count}") # Вывод: 3
4.	Решение:
class Team:
sport = "Футбол" # Атрибут класса
# Метод класса: принимает cis (ссылку на класс Team) @classmethod
def change_sport(cls: "Team", new_sport: str) -> None: # Используем cLs для изменения атрибута класса cis.sport = new_sport
Team.change_sport(‘Хоккей ) print(Team.spurt)
Ответы и решения 559
# Вывод: Хоккей
5.	Решение:
class Utility:
# Статический метод не принимает ни seif^ ни cLs
(Sstaticmethod
def is_valid_email(email_address: str) -> bool:
return in email_address and in email_address
emaill = "legolas777@example.ru"
print(Utility.is_valid_email(emai11)) # Вывод: True
email 2 = "big_thranduil“
print(Utility.is_valid_email(email?)) # Вывод: FaLse
5.6	Перегрузка арифметических операторов и операторов сравнения
1.	Если оператор не определён для левого операнда, Python пытается вызвать отражённый метод в правом операнде.
2.	Решение:
class Time: def______init__(self, total_seconas: int):
self.total_seconds = votal_seconds
def _str___(self) -> str:
return f "Time({self.rotal_seconds})"
def__mul___(self, other: int) -> 'Time":
if not isinstance(other, (int, float)): return Notlmplemented
new_value = self.total_seconds * other return Time(int(new_value))
print(Time(10) * 3)
# Вывод: Time(30)
3.	Решение:
class Time:
def___init__(self, total_seconds: int):
self.total_seconds = total_seconds
def __str___(self) -> str:
return f Time({self.total_seconds})"
Ответы и решения 560
def___mu]___(self, other: int) -> "Time":
if not isinstance(other, (int, float)): return Notlmplemented
new_value = self.total_seccnds * other return Time(int(new_value))
def___rmul__(self, other: int) -> "Time":
# Логика совпадает с прямым умножением return self.__mul__(other)
print(4 * Time(15))
# Вывод: Time(60)
4.	Решение:
class Item: def_______init__(self, name: str, price: float):
self.name = name self.price = price
def __eq__(self, other: object) -> bool:
if not isinstance(other, Item): return Notlmplemented
return self.name == other.name and self.price == other, price
print(Item( Атлас , 320) == Item("Атлас", 320)) # Вывод: True
5.	Решение:
class Item:
def___init__(self, name: str, price: float):
self.name = name self.price = price
def __It__(self, other: object) -> bool:
if not isinstance(other, Item):
return Notlmplemented
return se]f.price < other.price
print(Item( Блокнот", 500) < 11ет("Текстоиыделнтель', 60)) # Вывод: PaLse
Ответы и решения 561
5.7 Перегрузка операторов для создания контейнера
1.	Оба метода__iter___() и___next__() являются ключевыми для создания
итератора в Python. Метод____iter___() возвращает итератор, а метод___next___()
отвечает за возврат следующего элемента в последовательности при каждой итерации и вызывает исключение Stopiteration, когда элементы закапчиваются. Они используют ся вместе, потому что____iter___() создаёт объект, который умеет
итерироваться, а____next__() обеспечивает извлечение элементов из этого объ-
екта.
2.	Решение:
class StudentsGroup: def_______init__(self, students: listfstr], group_number: str):
self.students = students self.group_number = group_number
def _len_(self): return len(self.students)
students = [ "Карамзин "Соловьёв C.M.", "Ключевский E 0.", "Костомаров Н.И.", "Татищев- R.H.", "Покровский M.H.", "Рыбаков Б.А."
]
history_group = StudentsGroup(students, "Ист 401 ) print (len(history ..group)) # Вывод- 7
3.	Решение:
class Week: def ______init__(self):
self.days = [ "Понедельник", "Вторник ’, "Среда", "Четверг', "Пятница", "Суббота", "Воскресенье" ]
def __getitem__(self, index: int) -> str:
return self.daysfindex]
Ответы и решения 562
print(Week()[1]) # Вывод: Вторник
4.	Решение:
class Temperature:
def __init__(self, data: list[int, float], scale: str = "Цельсий"):
self.data = data self.scale = scale
def __contains__(self, item: int | float) -> bool:
return item in self.data
print(l in Temperature([0, -1, 5, 3, -2, -1, 1, 2, 2])) # Вывод: True
5.	Решение:
class GradeBook: def ______init__(self):
self.grades = {}
def __setitem___(self, student_name: str, grade: int | float) -> None:
if not isinstance(grade, (int, float)):
raise ТуреЕггог("Оценка должна быть числом1)
self.grades[student_name] = grade
grade_book - GradeBook()
grade_book[ 'Баженов В.И."] =4.5
grade_bo>'k["Казаков М.Ф. ] = 5.0
grade_book[ В.»оохин А.Н. J = 3.8
print(grade_boэк.grades)
# Вывод: {'Баженов В.И. ': 4.5, 'Казаков М.Ф. ': 5.0, 'Ворохин А.Н. ': 3.8}
Глава 6. Модули и пакеты
6.1	Введение в модули и пакеты
1.	Он создаёт пространство имен для импортируемого модуля, поэтому для того, чтобы обратиться к любому объекту этого модуля, необходимо использовать префикс, состоящий из имени модуля и точки.
2.	Значение переменной_паше_равно "_main_", если модуль запущен
Ответы и решения 563
напрямую, и совпадает с именем модуля, если он импортирован другим модулем.
3.	Решение:
#	data _utiLs.pv
def clean_text(text: str) -> str: return text.strip().upper()
#	main.py
import data_utils
input_string = " Данные бывают РаЗнЫе "
result = aata_utils.clean_text(input_string)
print(result)
#	Вывод: ДАННЫЕ БЫВАЮТ РАЗНЫЕ
4.	Решение:
#	тагп.ру
import data_utils as du
input_string2 = " Чёрные, белые, красные
result = au.clean_text(input_string2)
print(result)
#	Вывод: ЧЁРНЫЕj БЕЛЫЕ, КРАСНЫЕ
5.	Решение:
#	myjnath.py
def add(a: float, b: float) -> float: return a + b
def subtraci(a: float, b: float) -> float: return a - b
#	main.py
from my_math import add
result = add(lB, 5)
print(result)
#	Вывод: 15
6.2	Модуль math и математические вычисления
1.	По умолчанию функция math. log() использует число Эйлера в качестве основания. В математике такой логарифм называется натуральным.
Ответы и решения 564
2.	Решение:
import math
print(math.ceii(2.5))
#	Вывод: 3
print(math.floor(2.5))
#	Вывод: 2
3.	Решение:
import math
def calcuiate_area(radius: int | float) -> float: return math.pi * math.pow(radius, 2)
area = calculate_area(4) print(round(area, 2)) # Вывод: 50.27
4.	Решение:
import math
def get_tf'igonometry(angle_degrees: int | float) -> dictfstr, float]: angle_radians = math.radians(angle_degrees) return {
"sin": math.sin(angle_radians),
"cos": math.cos(angle_radians), "tan": math.tan(angle_radians) }
results = get_trigonometry(30)
for key, value in results.items(): print(f {key! = froundfvalue, 2)} )
#	Вывод: sin = 0.50
#	Вывод: cos = 0.87
it Вывод: tan = 0.58
5.	Решение:
import math
result = math.exp(math.logl0(5)) + math.sqrt(12) + math.cos(0.3) print(round(result, 3)) tt Вывод: 6.431
6.3	Модуль random и генерация случайных чисел
1.	Псевдослучайные числа не являются ио-настоящему случайными
Ответы и решения 565
числами и вычисляются на основе начального значения, которое называют seed. Установка начального значения с помощью функции random.seed() позволяет получать воспроизводимые результаты.
2.	Решение:
import random
def ranfioiri_temperature(min_temp: int | float, max_temp: int | float) -> float: return random.unifurm(min_temp, max_temp)
temperature = random_temperature(18.2, 25) print(temperature)
# Вывод: 24. Ы9054246494198
3.	Решение:
import random
participants = [’Анна", "Борис", "Светлана", "Дмитрий", "Елена ] initial_list = partici pants[:] # Копируем исходный список random.shuffle(participants)
print(f"CnncoK ди перемешивания: {initial_list}“)
#	Вывод: ['Анна', 'Борис', 'Светлана', 'Дмитрий', 'Елена'] print(f Список после перемешивания: {participants} )
#	Вывод- [‘ Елена', ‘Светлана', ‘Борис’, 'Дмитрий', ’Анна']
4.	Решение:
import random
directions = ["Север", "Юг", "Восток", "Запад"] random_directi on = random.choice(directions) print(random_direction)
# Вывод: Север
5.	Решение:
import random
def simuiate_event(probability: float) -> bool: return random.random() < probability
for _ in range(lOO):
if simulate_event(0.2):
true_count += 1
print(true_count)
Ответы и решения 566
# 19
6.4	Модуль datetime и работа с датой и временем
1.	Да, метод datetime.date() возвращает дату (объект date), а метод datetime.time() возвращает время (объект time).
2.	Решение:
from datetime import date
start_date = date(2025, 1, 15)
end_date = date(2025, 5, 20)
time_difference = end_date - start_date print(time_difference)
# Вывод: 125 days, 0:00:00
3.	Решение:
from datetime import datetime
date_str = "23 January 2026, 14:30"
format_str = "%d %B %Y,	# Формат: День Месяц Год, Час:Минута
datetime_obj = datetime.strptime(date_str, format_str) print(datetime_ obj)
# Вывод: 2026-01-23 14.30.00
4.	Решение:
from datetime import datetime
current_dt = datetime. novj()
formatted_string = current_dt.strftime('Сегодня: %A,	%d.%m.%Y. Бремя:
%H:%M:XS")
print(formatted_string)
# Вывод: Сегодня: Friday, 19.12.2025. Время: 01:11:37
5.	Решение:
from datetime import datetime
def is_event_passed(event_dt: datetime) -> bool: current_time = datetime.now()
# Если дата события (evenr_dt) меньше (<), значит, событие уже прошло return event_dt < current-time
past_dt = datetime(2025, 12, 17, 10, 0, 0) # Событие 3 прошлом print(f"Событие {past-dt} уже прошло: {is_event_passed(past_dt)} ) # Вывод: Событие 2025-12-17 10:00:00 уже прошло: True
future_dt = datetime(2026, 12, 1, 10, 0, 0) # Событие в будущем
print(f"Событие (past_dt) уже прошло: {is_event_passed(future_dt)}“)
Ответы и решения 567
# Вывод' Событие 2025-12-17 10:00:00 уже происпо: False
6.5	Установка сторонник пакетов. Виртуальное окружение
I.	Виртуальное окружение изолирует версии интерпретатора Python и библиотек одного проекта от других, что позволяет избежать конфликтов между разными версиями для разных проектов.
2.	Она выводит в терминале все установленные пакеты и их версии для текущей среды. Символ > позволяет перенаправить вывод с терминала в файл: pip freeze > requirements.txt
3.	Файл requirements.txt содержит все пакеты (и версии) проекта, что позволяет воспроизвести точно такое же виртуальное окружение на другом компьютере. Для установки пакетов из файла следует использовать команду pip install с флагом-г: pip install -г requirements.txt.
4.	Решение:
python -m venv .venv
Команда активации зависит от операционной системы. На Windows нужно запустить пакетный файл:
.venv\Scripts\activate
А на Linux или macOS запустить bash-скрипт:
source .venv/bin/activate
5.	Решение:
pip install pandas
6.	Решение:
pip show pandas
7.	Решение:
pip uninstall pandas
Глава 7. Работа с файлами
7.1	Чтение и запись файлов
I.	Байты внутри текстового файла представляют символы, которые мы видим на экране, а внутри бинарного файла - структуры данных, инструкции, аудиоданные и так далее.
Ответы и решения 568
2.	При открытии в режиме "а" указатель устанавливается в конец файла и его содержимое не перезаписывается. Режим "w" же полностью стирает содержимое файла и устанавливает указатель в его начало.
3.	Решение:
with open("notes.txt", "w", encoding= "utf-8') as notes_file: notes_f ile.write("Первая заметка\п") notes_file.write(‘Вторая заметка\п")
4.	Решение:
with open( notes.txt , "r1, encoding="utf-8") as notes_file: lines = notes_file.readlines()
print(lines)
# Вывод: [’Первая заметка\п’, ’Вторая заметка\п ’]
5.	Решение:
#	Создание файла prices.txt
with open("prices.txt', "w") as pricesfile: prices_file.writelines(["15.5\n", “22.3\n", "6.0\n"])
#	Чтение и вычисление
with open( prices.txt', "r") as prices_file:
numbers = [float(n) for n in prices_file.readlinesQ] average = sum(numbers) / len(numbers) print(average) # Вывод: 6
7.2	Работа с CSV-файлами
1	Стандартные методы чтения не учитывают cipyKiypy и специфичные правила форматирования CSV-файлов Например, па разных операционных системах по-разному обрабатывается символ перевода строки (\п или \г\п).
2.	Параметр fieldnames - список с названиями столбцов CSV-файла, который используется в качестве ключей словаря. Порядок названий в списке f ielnames определяет их порядок в выходном файле.
3.	Решение:
import csv
#	Создание файла data = [
["ФИО", "Курс", "Специальность", "Средний балл"], ["Толстой Лев Николаевич", 3, "Филология , 4.8], ["Достоевский Федор Михайлович", 2, "История , 4.5], ["Чехов Антон Павлович", 4, "Медицина1 , 4.9], ["Пушкин Александр Сергеевич”, 1, "Журналистика", 4.2],
Ответы и решения 569
[“Гоголь Николай Васильевич'3, "Искусствоведение", 4.6],
]
with npen( students.csv’w", newline=" , encoding="utf 8") as students:
writer = csv.writer(students)
writer.writerows(data)
#	Чтение файла
with open( students.csv", "r , newline=" , encoding="utf-8") as students:
reader = csv.reader(students)
#	Пропускаем заголовок (первая строка)
next(reader)
#	Используем остальные строки
specialties = [row[2] for row in reader]
print(specialties)
#	Вывод: [Филология', ’История', 'Медицина', 'Журналистика', 'Искусствоведение ' ]
4.	Решение:
import csv
with эреп( students.csv", "г , newline= ", encoding="utf• ) as students:
dict_reaaer = csv.DictReader(students)
for student in dict_reader:
#	Обращаемся к данным no ключам (заголовкам столбцов)
print(f'{student[ 'ФИО' ]}: {student["Специальность’ ]}")
#	Вывод: Толстой Лев Николаевич: Филология
#	Вывод: Достоевский Федор Михайлович: История
#	Вывод: Чехов Антон Павлович: Медицина
#	Вывод: Пушкин Александр Сергеевич: Журналистика
#	Вывод: Гоголь Николай Васильевич: Искусствоведение
5.	Решение:
import csv
with open( students.csv”, "a", newline= , encoding="utf-8 ) as students:
writer = csv.writer(students)
writer.writerow(['Бунин Иван Алексеевич", 3, "Лингвистика1, 5])
with open( students.csv", "r , newline* ", encoding="utf-r ) as students:
dict_reader = csv.DictReader(students)
students = [student["ФИО ] for student in dict_reader] print(students)
# Вывод: [‘Толстой Лев Николаевич’, 'Достоевский Феоор Михайлович', 'Чехов
Антон Павлович', 'Пушкин Александр Сергеевич', 'Гоголь Николай Васильевич', 'Бунин Иван Алексеевич']
Ответы и решения 570
7.3	Работа с JSON-файлами
1.	Объекты соответствуют словарям, а массивы - спискам.
2.	Функции json. load() и json.dump() предназначены для работы с JSON-файлами, а функции json.loads() и json.dumps() - для работы со строками в JSON-формате.
3.	Решение:
import json
with open( user.json'j "w", encoding='utf-8‘) as f:
user_data = {'id": 1, "username": "cooer_pro , "active": True} json.dump(user_data, f, indent=2)
4.	Решение:
import json
with open("user.json' j ’r', encodingsutf-8) as f:
read_user_data = json.load(f)
username = read_user_data.get("username") print(username)
# Вывод: coder_pro
5.	Решение:
import json
books = [
{'title": "Преступление и наказание", 'year": 18b6},
{"title": "Война и мир", "year": 1869}
]
json_string = json.dumps(books, ensure_ascii=False)
is_string = isinstance(json_string, str)
print(is_string)
# Вывод: True
7.4	Сохранение и восстановление объектов
1.	Данные сохраняются в двоичном формате в файлах с расширением pkl. Они должны открываться в бинарном режиме (с буквой «Ь» в конце режима, например, "rb")
2.	Нет, так как модуль picke сохраняет данные, а не код класса.
2.	Какое условие должно быть выполнено в текущей запущенной программе для того, чтобы успешно восстановить экземпляр класса из pickle-файла?
3.	Решение:
taskl = Task('Написать отчёт', "2025 12-05")
Ответы и решения 571
with open('taskl.pkl", "wb") as task_backup: pickle.dump(taskl, task_backup)
4.	Решение:
with upen( "taskl.pkl", "rb") as task_backup: restored_task = pickle.load(task_backup) print(restored_task.title) # Вывод: Написать отчёт
5.	Решение:
with open(‘taskl.pkl", "rb1) as task_backup: restored_task = pickle.load(task_backup) byte_string = pickle.dumps(restored_task) prirt(type(byte_string)) # Вывод: ccLass 'bytes'>
7.5	Взаимодействие с файловой системой
1.	Функция os .mkdir() создаёт только последнюю папку в пути, а функция os .makedirs() может создать все несуществующие папки, необходимые для формирования пути.
2.	Нужно воспользоваться функцией shutil.rmtree(). которая рекурсивно обходит папку и удаляет всё содержимое, а затем удаляет и саму папку.
3.	Решение:
import os
#	Создадим папку
folder_name = logs"
os.mkdir('logs")
print(f"Папка '{folder_name}' успешно создана')
#	Вывод: Папка 'Logs' успешно создана
#	Удалим папку
os.rmdir(folder_name)
print(f"Папка '{folder_name} успешно удалена")
#	Вывод: Папка 'Logs' успешно удалена
4.	Решение:
import os
with open( config_old.ini", 'w') as config:
pass
os.rename( config_old.ini", "ccnfig.ini")
Ответы и решения 572
5.	Решение:
import shutil
def delete_dir(path: str) -> None:
t ry:
shutil.rmtree(path)
print(f’Папка '{path}' успешно удалена")
except Exception as e:
print(f"Возникло исключение: {e})")
delete_dir("rion_existent_path")
# ВыЬод: Возникло исключение: [Errno 2] No such fiLe or directory: 'non_exist-ent_path')
Ответы и решения 573