commit 53d5933c798bb020bbdf0b988b5d31fcb5e43992 Author: Alexandr Pilshchikov Date: Sat Oct 21 22:03:12 2023 +0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ecb6e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.ipynb_checkpoints +*/.ipynb_checkpoints/* +*.swp +.DS_Store +.idea diff --git a/1. Введение в Python/1. Введение в Python.docx b/1. Введение в Python/1. Введение в Python.docx new file mode 100644 index 0000000..c4b4011 Binary files /dev/null and b/1. Введение в Python/1. Введение в Python.docx differ diff --git a/1. Введение в Python/1. Введение в Python.pdf b/1. Введение в Python/1. Введение в Python.pdf new file mode 100644 index 0000000..7b3d811 Binary files /dev/null and b/1. Введение в Python/1. Введение в Python.pdf differ diff --git a/1. Введение в Python/1. Знакомство с курсом/Опрос.ipynb b/1. Введение в Python/1. Знакомство с курсом/Опрос.ipynb new file mode 100644 index 0000000..af08934 --- /dev/null +++ b/1. Введение в Python/1. Знакомство с курсом/Опрос.ipynb @@ -0,0 +1,136 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Опрос #\n", + "\n", + "Уважаемые слушатели курса «Основы программирования на Python»!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Прошу Вас заполнить опрос, который разработан для изучения общей аудитории курса.\n", + "\n", + "Это займет у Вас не более 5 минут, но я смогу узнать о Вас больше информации, а значит сделать курс еще полезнее для Вас!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "##### 1. Оцените уровень Вашей подготовки в программировании на Python.\n", + " \n", + "- [ ] Высокий\n", + "- [ ] Средний\n", + "- [ ] Начальный\n", + "- [ ] Нулевой" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "##### 2. Был ли у Вас опыт обучения программированию на Python до начала прохождения курса?\n", + "\n", + "- [ ] Нет, опыт обучения отсутствовал\n", + "- [ ] Да, я изучал статьи и литературу, проходил онлайн-курсы\n", + "- [ ] Да, я посещал очные учебные курсы\n", + "- [ ] Да, у меня есть дипломы/сертификаты образовательных программ" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "##### 3. Был ли у Вас опыт работы с применением программирования на Python?\n", + "\n", + "- [ ] Нет, не было\n", + "- [ ] Да, был непродолжительный опыт\n", + "- [ ] Да, был продолжительный опыт" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "##### Что Вы ожидаете от курса:\n", + "___\n", + "___\n", + "___" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "[Далее...](../2.%20Первые%20шаги/О%20языке.ipynb)" + ] + } + ], + "metadata": { + "celltoolbar": "Слайд-шоу", + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/1. Знакомство с курсом/Опрос.pdf b/1. Введение в Python/1. Знакомство с курсом/Опрос.pdf new file mode 100644 index 0000000..d2c86fc Binary files /dev/null and b/1. Введение в Python/1. Знакомство с курсом/Опрос.pdf differ diff --git a/1. Введение в Python/1. Знакомство с курсом/Приветствие.ipynb b/1. Введение в Python/1. Знакомство с курсом/Приветствие.ipynb new file mode 100644 index 0000000..ce7a1ca --- /dev/null +++ b/1. Введение в Python/1. Знакомство с курсом/Приветствие.ipynb @@ -0,0 +1,143 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Приветствие #\n", + "\n", + "Рад приветствовать вас на курсе «Основы программирования на Python»!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Почему именно Python? ##\n", + "\n", + "**Python** - это простой, гибкий, популярный, и, что самое главное, востребованный язык программирования, на котором можно написать практически всё.\n", + "\n", + "Создавать веб-проекты, заниматься машинным обучением и анализом данных, автоматизировать задачи системного администрирования и многое другое." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Создавая этот курс, я стремился покрыть все темы, необходимые разработчику каждый день.\n", + "\n", + "Однако курс — это не просто набор лекций.\n", + "\n", + "Вас также ждёт большое количество практических примеров и задач.\n", + "\n", + "Именно это позволит вам оттачивать ваши знания и навыки по мере их получения." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "В первом блоке курса мы познакомимся с интерпретатором Python, посмотрим на основные конструкции и типы языка." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Затем нас ждёт знакомство со структурами данных и с функциями." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Дальше вас ждёт погружение в мир объектно-ориентированного программирования." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "В пятом блоке курса вы познакомитесь с многопоточным и асинхронным программированием." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Чтобы научиться программировать на Python, нужно как можно быстрее начать разрабатывать свои собственные настоящие программы.\n", + "\n", + "У вас будет возможность заняться этим вплотную на шестом блоке обучения.\n", + "\n", + "В итоговом проекте вы создадите своё сетевое клиент-серверное приложение.\n", + "\n", + "С подобными задачами сталкиваются разработчики в нашем институте." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Готовы? Давайте уже начнём программировать. [Далее...](Опрос.ipynb)" + ] + } + ], + "metadata": { + "celltoolbar": "Слайд-шоу", + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/2. Первые шаги/example.py b/1. Введение в Python/2. Первые шаги/example.py new file mode 100644 index 0000000..b835ab4 --- /dev/null +++ b/1. Введение в Python/2. Первые шаги/example.py @@ -0,0 +1,2 @@ +# example.py +print("hello") \ No newline at end of file diff --git a/1. Введение в Python/2. Первые шаги/guido.jpg b/1. Введение в Python/2. Первые шаги/guido.jpg new file mode 100644 index 0000000..187eb47 Binary files /dev/null and b/1. Введение в Python/2. Первые шаги/guido.jpg differ diff --git a/1. Введение в Python/2. Первые шаги/logo.png b/1. Введение в Python/2. Первые шаги/logo.png new file mode 100644 index 0000000..00e6eea Binary files /dev/null and b/1. Введение в Python/2. Первые шаги/logo.png differ diff --git a/1. Введение в Python/2. Первые шаги/logo.svg b/1. Введение в Python/2. Первые шаги/logo.svg new file mode 100644 index 0000000..a16973b --- /dev/null +++ b/1. Введение в Python/2. Первые шаги/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/1. Введение в Python/2. Первые шаги/pycharm1.png b/1. Введение в Python/2. Первые шаги/pycharm1.png new file mode 100644 index 0000000..3ac5b7d Binary files /dev/null and b/1. Введение в Python/2. Первые шаги/pycharm1.png differ diff --git a/1. Введение в Python/2. Первые шаги/pycharm2.png b/1. Введение в Python/2. Первые шаги/pycharm2.png new file mode 100644 index 0000000..6077b73 Binary files /dev/null and b/1. Введение в Python/2. Первые шаги/pycharm2.png differ diff --git a/1. Введение в Python/2. Первые шаги/pycharm3.png b/1. Введение в Python/2. Первые шаги/pycharm3.png new file mode 100644 index 0000000..182ef3d Binary files /dev/null and b/1. Введение в Python/2. Первые шаги/pycharm3.png differ diff --git a/1. Введение в Python/2. Первые шаги/pycharm4.png b/1. Введение в Python/2. Первые шаги/pycharm4.png new file mode 100644 index 0000000..eb01ed0 Binary files /dev/null and b/1. Введение в Python/2. Первые шаги/pycharm4.png differ diff --git a/1. Введение в Python/2. Первые шаги/python.png b/1. Введение в Python/2. Первые шаги/python.png new file mode 100644 index 0000000..4fee720 Binary files /dev/null and b/1. Введение в Python/2. Первые шаги/python.png differ diff --git a/1. Введение в Python/2. Первые шаги/Выбор среды разработки.ipynb b/1. Введение в Python/2. Первые шаги/Выбор среды разработки.ipynb new file mode 100644 index 0000000..b58b109 --- /dev/null +++ b/1. Введение в Python/2. Первые шаги/Выбор среды разработки.ipynb @@ -0,0 +1,197 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Выбор среды разработки (IDE) #" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Писать код можно в любом текстовом редакторе, однако специализированные редакторы и полноценные среды разработки (IDE) добавляют процессу программирования массу удобств – таких как подсветка синтаксиса, автодополнение, интроспекция кода (возможность по клику перейти к месту объявления используемого класса или функции) и многие другие." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Если вы не уверены, в чем вам программировать – попробуйте PyCharm Community Edition. Это полноценная IDE, которая позволит вам даже запускать код на Python, не выходя из редактора. Чтобы установить PyCharm Community Edition, перейдите по ссылке https://www.jetbrains.com/pycharm/download/ – вам будет предложено скачать на выбор Professional или Community версию среды разработки PyCharm. Professional версия – платная, для полноценной работы с Python будет достаточно бесплатной Community версии. Скачайте установщик PyCharm Community Edition для вашей операционной системы. После этого запустите установщик и следуйте инструкциям." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Также можете посмотреть в сторону Visual Studio Code (https://code.visualstudio.com/Download) – для разработки на Python вам дополнительно потребуется установить плагин \"Python\".\n", + "\n", + "Если вы программист с опытом, то возможно вам будет удобно писать код на Python в редакторах Vim (http://www.vim.org/) или Emacs (http://www.gnu.org/software/emacs/)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Среди других вариантов можно отметить редактор Atom (https://atom.io/), а также любимый многими за простоту и расширяемость Sublime Text (https://www.sublimetext.com/) или Notepad++ (https://notepad-plus-plus.org/).\n", + "\n", + "Особенно хочу выделить систему Anaconda (https://www.anaconda.com/), которая содержит в себе IDE Spyder (https://www.spyder-ide.org/) и крайне удобную подсистему Jupyter (https://jupyter.org/), которую в ходе курса я не раз продемонстрирую.\n", + "\n", + "Как вы можете видеть – редакторов и IDE много, выбирайте по вкусу. Я же подробно опишу процесс создания нового Python проекта в PyCharm Community Edition – если не знаете какой редактор выбрать, советую остановиться именно на нем." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Создание нового проекта в PyCharm Community Edition ##\n", + "\n", + "Когда PyCharm открылся нажмите New Project (Новый Проект)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "![PyCharm](pycharm1.png \"PyCharm\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Задайте путь и имя проекта, а также выбирите интерпретатор Python 3, который вы установили. Если вы не знаете, где на файловой системе находится путь до интерпретатора, – вот как можно его найти:\n", + "\n", + "- На Windows в терминале наберите `where python3` (или `where python` – в зависимости от того, как у вас запускается Python 3)\n", + "- На Unix-системах (Linux, MacOS ...) наберите в терминале `which python3` (или `which python` – в зависимости от того, как у вас запускается Python 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "![PyCharm](pycharm2.png \"PyCharm\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "После задания имени проекта и пути до интерпретатора в окне PyCharm нажмите кнопку Create.\n", + "\n", + "Откроется окно редактора. Слева на боковой панели нажмите на файле main.py. Файл откроется в окне редактора с примером кода на Python." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "![PyCharm](pycharm3.png \"PyCharm\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "В меню щелкните Run -> Run. Внизу должно появиться окно терминала, и в нем должен присутствовать вывод нашей программы." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "![PyCharm](pycharm4.png \"PyCharm\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Дополнительные материалы ##\n", + "\n", + "- Выбираем самый удобный редактор кода Python (https://habr.com/ru/company/skillfactory/blog/521838/)\n", + "\n", + "[Далее...](Начинаем%20программировать.ipynb)" + ] + } + ], + "metadata": { + "celltoolbar": "Слайд-шоу", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/2. Первые шаги/Начинаем программировать.ipynb b/1. Введение в Python/2. Первые шаги/Начинаем программировать.ipynb new file mode 100644 index 0000000..6d37461 --- /dev/null +++ b/1. Введение в Python/2. Первые шаги/Начинаем программировать.ipynb @@ -0,0 +1,543 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Начинаем программировать #\n", + "\n", + "Теперь, когда Python 3 установлен в систему и я надеюсь вы выбрали редактор или среду разработки, в которых будете писать код на Python'е, мы можем начать программировать." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Первое на что мы посмотрим - это интерактивный интерпретатор Python. Чтобы запустить интерактивный интерпретатор, нам нужно в терминале набрать команду `python3`. Обратите внимание, что в зависимости от вашей системы и способа, которым вы устанавливали интерпретатор python'а 3 в систему, запуск, имя команды может отличаться и, например, быть просто `python`. В моем случае это `python3`. Обратите внимание, что когда я запускаю, версия интерпретатора указана вот здесь, в начале приветственной строки и нам нужна версия >=3.6.0.\n", + "\n", + "![Python](python.png \"Python\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Интерактивный интерпретатор, по большому счету, - это программа, которая считывает ввод пользователя, интерпретирует его и выдает на экран результат. Интерпретатор - это то, что делает Python таким доступным. В любой момент вы можете открыть интерактивную оболочку и проверить какую-то свою гипотезу, поэкспериментировать или просто воспользоваться ей как калькулятором." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Давайте это и сделаем. Мы можем сложить два числа, либо поделить два числа." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 + 1" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.5" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 / 2" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Давайте попробуем напечатать на экран какую-нибудь строку, используя для этого встроенную функцию `print`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello\n" + ] + } + ], + "source": [ + "print(\"hello\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "У нас получилось. В любой момент, находясь в интерактивном интерпретаторе, мы можем воспользоваться встроенной функцией help. Встроенная функция help позволяет получить справку по любому объекту, который доступен в области видимости. Посмотрим справку по объекту help. Это функция, и мы видим, что справка вывелась на экран, описание, что такое функция `print`, какие аргументы она принимает." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on built-in function print in module builtins:\n", + "\n", + "print(...)\n", + " print(value, ..., sep=' ', end='\\n', file=sys.stdout, flush=False)\n", + " \n", + " Prints the values to a stream, or to sys.stdout by default.\n", + " Optional keyword arguments:\n", + " file: a file-like object (stream); defaults to the current sys.stdout.\n", + " sep: string inserted between values, default a space.\n", + " end: string appended after the last value, default a newline.\n", + " flush: whether to forcibly flush the stream.\n", + "\n" + ] + } + ], + "source": [ + "help(print)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Для того чтобы из справки выйти, мы можем нажать клавишу `q`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Сейчас мы ненадолго расстанемся с интерактивным интерпретатором. Чтобы выйти из него, можно нажать `Ctrl + D`, либо использовать доступную в интерпретаторе функцию `exit`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "А сейчас давайте поговорим о том, как в реальных проектах пишут код на Python'е. В реальных проектах код на Python'е пишут конечно же не в интерактивном интерпретаторе, а в файлах. А в больших проектах - в большом количестве файлов. Скоро мы вас научим как структурировать код на Python'е по модулям и пакетам, а сейчас нашей целью будет создать простейший файл, который будет содержать код на Python'е и запустить его интерпретатором Python. Давайте это сделаем. Я буду использовать текстовый редактор `vim`, для того чтобы создать файл. Обратите внимание, что я назвал файлик `example.py` и использовал расширение `py`. Это то расширение, которое принято давать файлам, которые содержат код на Python. Внутри файлика напишем простейшую инструкцию `print(\"hello\")`, сохраним его. Теперь, чтобы запустить, мы можем воспользоваться командой `python3`. В качестве аргумента передать ей имя нашего файла, который только что создали, и программа исполнится." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello\n" + ] + } + ], + "source": [ + "! python example.py" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Продолжим. Сейчас мы вернемся в интерактивный интерпретатор и поговорим с вами о переменных. Переменная - это то, что позволяет сохранить результат выполнения выражения для того, чтобы использовать его в дальнейшем. Наверняка вы встречались с переменными в других языках программирования. В Python'е для того, чтобы объявить переменную нужно написать следующее, например, `num = 1`. Мы используем знак `=`. Это оператор присваивания. В данном случае мы связываем имя переменной `num` с целочисленным объектом со значением `1`. В любой момент мы можем переприсвоить имени `num` значение другое - `num = 2`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "num = 1" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "num = 2" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Также в любой момент мы можем присвоить переменной `num` объект другого типа. В данном случае это строковый объект." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "num = \"hello\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Поговорим о названиях переменных в Python. В Python имена переменных могут содержать буквы, цифры и символ нижнего подчеркивания. При этом начинаться переменная должна либо с буквы, либо с символа нижнего подчеркивания, то есть вот это правильное название переменной в Python, а вот, например, вот такое название уже будет неверным." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "hello_world = \"Hello, world!\"" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "SyntaxError", + "evalue": "invalid decimal literal (, line 1)", + "output_type": "error", + "traceback": [ + "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m1\u001b[0m\n\u001b[1;33m 42_answer = 42\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid decimal literal\n" + ] + } + ], + "source": [ + "42_answer = 42" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "В данном случае происходит ошибка, выбрасывается исключение, это синтаксическая ошибка. Мы видим `SyntaxError`. В дальнейших лекциях нашего курса я научу вас обрабатывать эти исключения. Что важно отметить еще это то, что если переменная состоит из нескольких слов, то есть это длинное название переменной, то ее принято называть так называемым snake_case'ом. То есть слова начинаются с маленькой буквы, и отдельные слова разделены символом нижнего подчеркивания. В других языках вы могли видеть, что переменные называются в так называемом camelCase'ом. В Python'е так делать не принято.\n", + "\n", + "Свод рекомендаций в оформлению кода представлен в PEP8 (https://www.python.org/dev/peps/pep-0008/)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Поговорим еще об одной особенности синтаксиса Python, а именно о том, как отделять блоки кода. В других языках вы могли видеть, что блоки кода отделяются фигурными скобочками, в Python'е не так. В Python'е блоки кода нужно отделять с помощью отступов.\n", + "\n", + "Что это значит? Давайте напишем небольшую программку, которая будет печатать на экран числа от 0 до 3. Обратите внимание, что блок кода внутри конструкции for, о которой мы еще с вами будем говорить в дальнейшем, я отделил с помощью четырех пробелов." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n", + "3\n" + ] + } + ], + "source": [ + "for item in range(4):\n", + " print(item)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Это то, как принято отделять блоки кода в Python. Вам, конечно, не придется каждый раз нажимать клавишу Пробел четыре раза. Зачастую это будет делать за вас редактор или IDE, которое вы используете. Последнее на что мы посмотрим, это то как комментировать код. Давайте вернемся в файл `example.py`, который создавали незадолго до этого и попробуем написать комментарий." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "# комментарий" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "В Python'е комментарий пишется с использованием специального символа \"решетка\", после которого идет текст комментария. Также комментарий можно писать на строках с выражениями. Это так называемые `inline` комментарии." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "'''\n", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n", + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n", + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n", + "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n", + "'''\n", + "\n", + "num = 42 # Answer to the Ultimate Question of Life, The Universe, and Everything" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Также вы можете увидеть, что время от времени в программах встречаются многострочные комментарии. Однако этот комментарий не игнорируется интерпретатором Python. Это строковый литерал, который зачастую вы будете видеть, как часть документации функции, либо класса. Этот строковый литерал становится частью объекта, к которому можно достучаться, используя специальное свойство `doc` у объекта." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "print(value, ..., sep=' ', end='\\n', file=sys.stdout, flush=False)\n", + "\n", + "Prints the values to a stream, or to sys.stdout by default.\n", + "Optional keyword arguments:\n", + "file: a file-like object (stream); defaults to the current sys.stdout.\n", + "sep: string inserted between values, default a space.\n", + "end: string appended after the last value, default a newline.\n", + "flush: whether to forcibly flush the stream.\n" + ] + } + ], + "source": [ + "print(\n", + " print.__doc__\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Давайте на это посмотрим. Например, функция print, которую мы использовали содержит атрибут doc, в котором содержится строка документирования, которая определена у этой функции." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "В этой лекции мы с вами познакомились с интерактивным интерпретатором Python, посмотрели на особенности синтаксиса Python, поговорили о том, как отделять блоки кода в Python'е, как называть переменные и написали первую небольшую программу в отдельном файле. В дальнейшем нас ждет знакомство с основными типами, которые есть в языке." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Дополнительные материалы к лекции ##\n", + "\n", + "- REPL — Википедия (https://ru.wikipedia.org/wiki/REPL)\n", + "- Как запустить скрипт на Python (https://mkdev.me/posts/kak-zapustit-skript-na-python)\n", + "- Комментирование Python кода (https://dev-gang.ru/article/kommentirovanie-python-koda-auf6lgv2vv/)\n", + "- Правильный выбор имен переменных в Python (http://pythonlearn.ru/python-dlya-nachinayushhix/imena-dlya-peremennyx-v-python/)" + ] + } + ], + "metadata": { + "celltoolbar": "Слайд-шоу", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/2. Первые шаги/О языке.ipynb b/1. Введение в Python/2. Первые шаги/О языке.ipynb new file mode 100644 index 0000000..f1beea7 --- /dev/null +++ b/1. Введение в Python/2. Первые шаги/О языке.ipynb @@ -0,0 +1,189 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# О языке #\n", + "\n", + "Давайте сначала немножко поговорим об истории языка." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "В конце 80-х годов прошлого века сотрудник голландского центра математики и информатики Гвидо ван Россум решил создать свой собственный язык." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "![guido.jpg](guido.jpg)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Основной целью он ставил создать язык простой и выразительный, на котором было бы просто писать код.\n", + "\n", + "В 1991 году он опубликовал исходники языка, который получил название Python. \n", + "\n", + "Не в честь известного всем вида пресмыкающихся, а в честь популярного телешоу 70-х годов прошлого века \"Летающий цирк Монти Пайтона\".\n", + "\n", + "Однако это не помешало змее стать маскотом, символом языка, а также присутствовать на логотипах языка и связанных с ним проектах." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "![logo.png](logo.svg)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Сейчас, спустя много лет, мы видим, что у Гвидо получилось.\n", + "\n", + "У него получилось создать интерпретируемый язык с динамической типизацией и автоматической сборкой мусора, на котором действительно приятно писать код." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Читая код \"на питоне\", мы практически читаем книгу на английском языке.\n", + "\n", + "Конечно это преувеличение, однако Python действительно выделяется в этом отношении.\n", + "\n", + "Также за годы существования языка вокруг него сложилось огромное сообщество, и была написана масса готовых библиотек на все случаи жизни.\n", + "\n", + "Я ставлю цель сделать вас частью этого сообщества по завершению этого курса." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Разработчики языка, выпустили третью версию, обратно несовместимую со второй. Это было сделано специально для того, чтобы решить некоторые архитектурные недостатки второй версии языка. Это получилось сделать, однако обратная несовместимость привела к тому, что до сих пор многие продакшн-системы используют Python версии 2. Но в 2020 году настал дедлайн и официальная поддержка Python 2 прекратилась, поэтому новые проекты стоит начинать именно на Python'е 3. Тем более, если вдруг вам потребуется узнать отличие Python'а 2 от Python'а 3, вы сможете это сделать достаточно быстро за 1–2 дня. Всё это есть в Интернете. На курсе я буду рассказывать про Python 3, а именно Python версии 3.6." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Python — это название спецификации языка, основная его реализация написана на языке C, называется CPython. Есть и другие реализации спецификации языка Python такие, как IronPython для .NET, либо PyPy, который добавляет JIT-компиляцию коду. Однако в курсе, когда я буду говорить слово Python, я имею в виду именно реализацию на C — CPython. Исходный код CPython открыт и доступен по ссылке на github https://github.com/python/cpython." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "У Python великолепная документация с большим количеством примеров, которые ставят в пример другим языкам, она также доступна в Интернете по ссылке https://docs.python.org/3.6/." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Развитие языка Python происходит согласно чётко регламентированному процессу создания, обсуждения, отбора и реализации документов PEP.\n", + "\n", + "PEP - Python Enhancement Proposal - это предложения по развитию питона https://www.python.org/dev/peps/.\n", + "\n", + "Процесс PEP является основным механизмом для предложения новых возможностей и для документирования проектных решений, которые прошли в Python." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Теперь, прежде чем начать программировать, нам осталось решить два вопроса.\n", + "\n", + "Первый — это как установить Python 3 в систему, и второй — в каком редакторе писать код на Python'е.\n", + "\n", + "Давайте разберемся с этими вопросами. [Далее...](Установка%20Python%203.ipynb)" + ] + } + ], + "metadata": { + "celltoolbar": "Слайд-шоу", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 1, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/2. Первые шаги/Полезные ссылки.ipynb b/1. Введение в Python/2. Первые шаги/Полезные ссылки.ipynb new file mode 100644 index 0000000..b791cec --- /dev/null +++ b/1. Введение в Python/2. Первые шаги/Полезные ссылки.ipynb @@ -0,0 +1,72 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Полезные ссылки #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Python – язык с огромной экосистемой. Чтобы помочь вам в этой экосистеме ориентироваться, в этом документе я постараюсь дать некоторые полезные советы и ссылки для начинающих программировать на Python.\n", + "\n", + "Начнем с основной ссылки – документации Python 3. Документация Python чрезвычайно подробна и наполнена большим количеством примеров – ее часто ставят в пример другим языкам (https://docs.python.org/3/).\n", + "\n", + "Если вы знаете язык программирования C, то в любой непонятной ситуации вы всегда можете заглянуть в исходный код CPython, который доступен на Github (https://github.com/python/cpython).\n", + "\n", + "Красивый код – это одна из мантр языка Python. Все Python-программисты стараются следовать советам по стилю кода, описанным в документе PEP 8 (https://www.python.org/dev/peps/pep-0008/).\n", + "\n", + "Есть утилита autopep8, которая позволяет автоматически приводить код к виду, соответствующему PEP 8 (https://pypi.python.org/pypi/autopep8).\n", + "\n", + "Библиотеки, написанные сообществом, находятся на ресурсе PyPI (Python Package Index) (https://pypi.python.org/pypi).\n", + "\n", + "Именно с этого ресурса будут устанавливаться внешние пакеты, когда вы будете устанавливать их с помощью утилиты pip. Лучший способ найти библиотеку для решения той или иной задачи – постараться загуглить ее – часто поиск Google выдает наиболее релевантный вариант. На GitHub есть коллекция хороших библиотек для решения всевозможных задач (https://github.com/vinta/awesome-python).\n", + "\n", + "Большая база вопросов и ответов по Python сосредоточена на ресурсе Stack Overflow (https://stackoverflow.com/) – вы будете часто натыкаться на него, когда будете искать решение в непонятных ситуациях.\n", + "\n", + "Большинство из материалов по ссылкам выше на английском языке. Однако и в русском сегменте интернета информации по Python достаточно. Обратите внимание, что многое из того, что вы найдете может относиться к Python второй версии – обращайте на это внимание, мы с вами изучаем Python 3. Хорошие ресурсы для новичков в Python на русском языке:\n", + "\n", + "https://pythonworld.ru/samouchitel-python\n", + "\n", + "https://metanit.com/python/tutorial/\n", + "\n", + "Также есть русскоязычная версия портала stackoverflow, где есть множество вопросов/ответов с тегом \"python\" (https://ru.stackoverflow.com/questions/tagged/python).\n", + "\n", + "Таблица cоответствия консольных команд Windows и Linux (https://white55.ru/cmd-sh.html).\n", + "\n", + "Не забывайте и про книги - их про Python очень много, в том числе и на русском языке. Опять же, обращайте внимание на версию языка, которая в них описана.\n", + "\n", + "Тем, кто любит книги, советуем обратить внимание на следующие издания:\n", + "\n", + "- Марк Саммерфилд - Программирование на Python 3. Подробное руководство\n", + "- Марк Лутц - Изучаем Python, 5-е издание\n", + "\n", + "Эти книги, особенно вторая, могут показаться тяжелыми для чтения, так как являются очень подробными справочниками по языку Python. Однако в них собраны практически все сведения о языке." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/2. Первые шаги/Работа в терминале.ipynb b/1. Введение в Python/2. Первые шаги/Работа в терминале.ipynb new file mode 100644 index 0000000..ac2930c --- /dev/null +++ b/1. Введение в Python/2. Первые шаги/Работа в терминале.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Работа в терминале #" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "В лекциях мы будем много времени проводить работая в терминале. Я буду использовать терминалы Unix-подобных операционных систем. Обратите внимание, что если вы пользуетесь командной строкой Windows – то команды, доступные там, будут немного отличаться от тех, что используются в лекциях. Это не должно вас смущать – это не имеет прямого отношения к программированию на языке Python, и для всех команд есть аналоги (соответствие команд можно найти на этой странице - https://white55.ru/cmd-sh.html)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Работа в терминале – это неотъемлемая часть профессии программиста, мы проводим в терминале очень много времени, выполняя в нем очень много полезных действий при разработке проектов. Однако, чтобы писать код на Python – вам совсем не обязательно пользоваться командной строкой. Достаточно установить PyCharm Community Edition, создать в нем новый проект – и можно начинать творить. Я расскажу про выбор IDE как установить PyCharm, создать в нем проект и запустить код." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Также на лекциях я буду использовать текстовый редактор vim, который запускается непосредственно в терминале. Если вы никогда ранее не пользовались этим редактором или у вас Windows – создавайте и редактируйте файлы с помощью любого другого текстового редактора. Я перечислю ряд специализированных редакторов для удобного программирования на Python в следующей теме. [Далее...](Выбор%20среды%20разработки.ipynb)" + ] + } + ], + "metadata": { + "celltoolbar": "Слайд-шоу", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/2. Первые шаги/Установка Python 3.ipynb b/1. Введение в Python/2. Первые шаги/Установка Python 3.ipynb new file mode 100644 index 0000000..50a0538 --- /dev/null +++ b/1. Введение в Python/2. Первые шаги/Установка Python 3.ipynb @@ -0,0 +1,268 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Установка Python 3 #\n", + "\n", + "В этой части я помогу вам с установкой Python 3.\n", + "\n", + "Во множестве операционных систем Python установлен по умолчанию, однако на данный момент это чаще всего Python версии 2.7, или не самая последняя версия Python 3. Наша цель установить Python 3.6+." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Windows ##" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Перейдите на сайт https://www.python.org/downloads/ и скачайте установщик последней доступной версии Python 3 для Windows." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Запустите установщик. На первом экране обязательно отметьте галочкой опцию `Add Python 3.6 to PATH` – это сделает Python 3 доступным в командной строке. Далее следуйте инструкциям, в процессе установки не снимайте галочки у предлагаемых для установки компонентов.\n", + "\n", + "После установки Python 3 и программа IDLE будут доступны в меню Start (Пуск)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "В лекциях я буду часто показывать примеры работы в терминале. На курсе я использую Unix-подобные системы, однако на Windows также есть командная строка." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Для использования Python из командной строки Windows необходимо установить должным образом переменную `PATH`. Мы прописали Python 3.6 в переменную `PATH` во время установки, поставив галочку напротив соответствующей опции.\n", + "\n", + "Давайте откроем командную строку и убедимся, что python3 доступен." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Чтобы открыть терминал в Windows:\n", + "\n", + "Если у вас Windows 8 и выше нажмите кнопку «Поиск» (значок лупы рядом с кнопкой «Пуск»), в панели поиска наберите «Выполнить» и запустите найденную программу.\n", + "Если у вас Windows версии ниже 8 нажмите кнопку «Пуск» и выберите «Выполнить».\n", + "Окно программы \"Выполнить\" можно также вызвать с помощью сочетания клавиш `Win+R`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "В появившемся диалоговом окне программы \"Выполнить\" наберите слово `cmd` и нажмите Enter. Запустится терминал.\n", + "\n", + "Затем наберите `python` - должен запуститься интерактивный интерпретатор. Если команда `python` не запустила интерпретатор – попробуйте `python3`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Вы должны увидеть что-то подобное:\n", + "\n", + "![Python](python.png \"Python\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "## MacOS ##" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Я опишу 2 возможных способа установить Python 3 на MacOS." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "### Способ 1. ###\n", + "\n", + "Перейдите по ссылке http://python.org/download/ и скачайте установщик последней доступной версии Python 3 для MacOS.\n", + "\n", + "Запустите установщик, запустите его. Запустите Python.mpkg в открывшемся окне.\n", + "\n", + "Во время установки вам нужно будет ввести административный пароль.\n", + "\n", + "После установки в папке /Applications появится IDLE." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "### Способ 2. ###\n", + "\n", + "С помощью утилиты brew: https://brew.sh/index_ru.html\n", + "\n", + "Вам нужно установить brew, а затем набрать в терминале:\n", + "```\n", + "# brew install python3\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "После установки попробуйте запустить приложение Терминал (установлено по умолчанию) – и наберите `python3` – должен запуститься интерактивный интерпретатор. Если команда `python3` не запустила интерпретатор – попробуйте просто `python`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Linux ##" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "В различных дистрибутивах Linux способы установки Python 3 могут отличаться, вы можете собрать Python 3 из исходного кода.\n", + "\n", + "Например в дистрибутивах основанных на Debian, можно использовать следующую команду: `$ sudo apt-get install python3`, а на RHEL: `$ sudo yum install python3`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Или собрать самостоятельно:\n", + "\n", + "```\n", + "# wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tgz\n", + "# tar xzvf Python-3.6.1.tgz\n", + "# cd Python-3.6.1\n", + "# ./configure --with-ensurepip=install\n", + "# make\n", + "# make install\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Замечания относительно устанавливаемой версии Python.\n", + "\n", + "Рекомендованная версия - 3.6. Но вы можете установить себе на компьютер любую версию Python начиная с 3.6. [Далее...](Работа%20в%20терминале.ipynb)" + ] + } + ], + "metadata": { + "celltoolbar": "Слайд-шоу", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/3. Базовые типы и конструкции/graph.svg b/1. Введение в Python/3. Базовые типы и конструкции/graph.svg new file mode 100644 index 0000000..93b6bdc --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/graph.svg @@ -0,0 +1,64 @@ + + + + + + + x + y + x + y + 2 + 2 + ( + ) + ; + x + y + 2 + y + 1 + 2 + x + 1 + x + y + 1 + 1 + ( + ) + ; + + + + + + + + + + + + + + + + + + + + + + + diff --git a/1. Введение в Python/3. Базовые типы и конструкции/main.py b/1. Введение в Python/3. Базовые типы и конструкции/main.py new file mode 100644 index 0000000..f05be1e --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/main.py @@ -0,0 +1,23 @@ +from random import randint + +number = randint(0, 101) + +while True: + answer = input("Введите число: ") + + if not answer or answer == "exit": + break + + if not answer.isdigit(): + print("Введите правильное число!") + continue + + user_answer = int(answer) + + if user_answer > number: + print("Загаданное число меньше") + elif user_answer < number: + print("Загаданное число больше") + else: + print("Совершенно верно!") + break \ No newline at end of file diff --git a/1. Введение в Python/3. Базовые типы и конструкции/Конструкции управления потоком.ipynb b/1. Введение в Python/3. Базовые типы и конструкции/Конструкции управления потоком.ipynb new file mode 100644 index 0000000..b658f5c --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/Конструкции управления потоком.ipynb @@ -0,0 +1,651 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Конструкции управления потоком #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Когда вы пишете программу, у вас постоянно возникает необходимость выполнить то или иное действие, тот или иной блок кода в зависимости от выполнения условия. Также может возникнуть необходимость какой-то блок кода выполнить несколько раз. Для этого существуют конструкции управления потоком. Об этих конструкциях мы и будем говорить в этой лекции." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Первое, на что мы посмотрим — это оператор `if`. Оператор `if` используется для выполнения каких-то действий при выполнении условия. Давайте посмотрим на пример." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Условие выполнено!\n" + ] + } + ], + "source": [ + "company = \"vniitf.ru\"\n", + "\n", + "if \"vniitf\" in company:\n", + " print(\"Условие выполнено!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "В данном примере мы определяем переменную `company` со значением `vniitf.ru` и далее используем оператор `if` и проверяем наличие подстроки `vniitf` в нашей строке. Если условие выполняется, а условие выполняется в том случае, если оно интерпретируется как `True`, то блок кода под `if` выполняется. В данном случае подстрока `vniitf` есть в строке `vniitf.ru` и наше условие выполняется.\n", + "\n", + "Иногда может быть так, что условие у вас сложное. Никаких проблем, вы можете записывать сложные условия в блоке оператора `if`, то есть вы можете выполнять различные bool'евские выражения, разделенные `or`, `and` и так далее. На втором примере как раз это показано." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Условие выполнено!\n" + ] + } + ], + "source": [ + "company = \"chelaxe.ru\"\n", + "\n", + "if \"vniitf\" in company or company.endswith(\".ru\"):\n", + " print(\"Условие выполнено!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "В некоторых случаях нужно выполнить какие-либо действия, когда условие не выполняется. Для этого существует оператор `else`. Оператор `else` в блоке `if…else` позволяет выполнить какой-то код, если условие не выполнилось. Посмотрим на примере." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "company = \"google.com\"\n", + "\n", + "if \"vniitf\" in company:\n", + " print(\"Условие выполнено!\")\n", + "else:\n", + " print(\"Условие не выполнено!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "У нас в данном случае строка `google.com`, мы проверяем условие — наличие подстроки `vniitf` в строке `google.com` Оно, конечно же, не выполняется, но благодаря оператору `else` мы можем это отловить и выполнить код, который печатает нам на экран то, что условие не выполнено. Также есть возможность использовать выражение `if…elif…else`, в котором добавился оператор `elif`. Он используется тогда, когда нужно проверить друг за другом какие-то условия, не связанные друг с другом. Опять же на примере посмотрим." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Подстрока google найдена\n" + ] + } + ], + "source": [ + "company = \"google.com\"\n", + "\n", + "if \"vniitf\" in company:\n", + " print(\"Подстрока vniitf найдена\")\n", + "elif \"google\" in company:\n", + " print(\"Подстрока google найдена\")\n", + "else:\n", + " print(\"Подстрока не найдена\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Мы определяем переменную со значением `google.com` строковый, и дальше у нас в данном случае срабатывает блок `elif`, который выполняется тогда, когда подстрока `google` была найдена в нашей строке.\n", + "\n", + "Также во многих языках программирования вы могли встречать тернарный оператор. В Python'е есть его аналог, и он записывается вот таким вот образом, который вы видите на примере." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Argentina\n" + ] + } + ], + "source": [ + "score_1 = 5\n", + "score_2 = 0\n", + "\n", + "winner = \"Argentina\" if score_1 > score_2 else \"Jamaica\"\n", + "\n", + "print(winner)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "То есть переменной будет присвоена строка `Argentina` в том случае, если условие выполнено, иначе будет присвоена строка `Jamaica`. Все это записывается в одну строчку, как вы можете видеть. В некоторых случаях это удобно.\n", + "\n", + "Перейдем к другому оператору, оператору `while`. `While` позволяет выполнять блок до тех пор, пока выполняется какое-то условие. Опять же давайте посмотрим на пример." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100\n" + ] + } + ], + "source": [ + "i = 0\n", + "while i < 100:\n", + " i += 1\n", + " \n", + "print(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Мы определяем переменную, `i = 0`, и будем прибавлять к переменной `i` единичку до тех пор, пока она меньше 100. Достаточно всё просто, мы пишем `while i < 100`, прибавляем единичку." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "i = 3\n", + "\n", + "while i >= 0:\n", + " i -= 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Сколько раз выполнится блок кода внутри цикла while?\n", + "\n", + "Дальше.\n", + "\n", + "Иногда вам нужно проитерироваться по какой-то последовательности. Для этого в Python существует выражение `for…in`. Мы на данный момент знаем две последовательности, о которых уже рассказывали — это строка и это байтовая строка В примере на слайде мы итерируемся по строке. Строка содержит значение Alex, и мы итерируемся с помощью выражения `for…in`, `for letter in name`, мы принтим буковку. Каждый юникодный символ друг за другом попадает в тело нашего for-цикла и с помощью функции `print` попадает в дальнейшем на экран." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A\n", + "l\n", + "e\n", + "x\n" + ] + } + ], + "source": [ + "name = \"Alex\"\n", + "\n", + "for letter in name:\n", + " print(letter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Встроенный объект `range` позволяет итерироваться по целым числам. Что это значит? Если мы напишем `for i in range(3)`, то в теле блока `for` мы сначала получим ноль, потом один, потом два, но не три. Когда мы определяем объект `range`, он не включает в себя последнее значение." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n" + ] + } + ], + "source": [ + "for i in range(3):\n", + " print(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте посмотрим на чуть более сложный пример." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5050\n" + ] + } + ], + "source": [ + "result = 0\n", + "\n", + "for i in range(101):\n", + " result += i\n", + "\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Это задачка. Может быть, многие слышали историю про Фридриха Гаусса, который решил ее на уроке школьной математики очень быстро. Уверен, что на Python'е мы решим ее не менее быстро и суть ее в том, чтобы получить сумму чисел от 0 до 100. С помощью цикла `for` мы очень просто можем это сделать.\n", + "\n", + "Объект `range` также может инициализироваться с двумя аргументами, для того чтобы проитерироваться по последовательности чисел, начиная с какого-то числа и заканчивая каким-то числом. Но опять же последнее число не будет включено в последовательность." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5\n", + "6\n", + "7\n" + ] + } + ], + "source": [ + "for i in range(5, 8):\n", + " print(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также есть возможность проинициализировать `range` с тремя аргументами, и последним аргументом будет шаг. В данном случае это проще всего понять на примере. Мы итерируемся по числам от одного до десяти с шагом два, и на экран выводятся только нечетные числа, потому что мы перескакиваем по два." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "3\n", + "5\n", + "7\n", + "9\n" + ] + } + ], + "source": [ + "for i in range(1, 10, 2):\n", + " print(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также есть возможность итерироваться по числам в обратном порядке. Для этого в качестве шага объекту range нужно передать −1. На примере видно, что мы от десяти до пяти, опять же пять не включительно, в обратном порядке прогулялись." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10\n", + "9\n", + "8\n", + "7\n", + "6\n" + ] + } + ], + "source": [ + "for i in range(10, 5, -1):\n", + " print(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Перейдем к оператору `pass`. Оператор `pass` — это оператор, определяющий пустой блок, который ничего не делает. В основном на практике вы будете использовать этот оператор в качестве заглушки на время, пока вам не нужно писать код в каком-то блоке, но потом вы к нему вернетесь и замените оператор `pass` на что-то более полезное. Однако иногда для создания простых классов-наследников оператор `pass` может играть и полезную роль. Об этом вам ещё будет рассказываться в следующих лекциях. На примере мы можем посмотреть, `for i in range(100)`, `pass`, то есть мы итерируемся по числам и не делаем ничего в блоке цикла." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "for i in range(100):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Следующий оператор, на который мы посмотрим — это оператор `break`. Оператор `break` позволяет выйти из цикла досрочно. Что это значит? Давайте посмотрим на простом примере.\n", + "\n", + "У нас есть цикл `while True`, это бесконечный цикл, потому что условие всегда `True`, и он работал бы бесконечно, потребляя ресурсы процессора, если бы не наш оператор `break`, который мы вставили в код. Он отработает тогда, когда переменная `result` становится больше 100. Это позволяет выйти из цикла `while`, и выполнение программы достигает выражения `print(result)` и мы видим результат выполнения. " + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100\n" + ] + } + ], + "source": [ + "result = 0\n", + "\n", + "while True:\n", + " result += 1\n", + " if result >= 100:\n", + " break\n", + "\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Оператор `break` также работает и в циклах `for`, то есть если мы используем `break` внутри `for`, мы выходим из `for`." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n", + "3\n", + "4\n" + ] + } + ], + "source": [ + "for i in range(10):\n", + " if i == 5:\n", + " break\n", + " print(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также давайте посмотрим на оператор `continue`. Оператор `continue` позволяет перейти к следующей итерации в блоке цикла, не выполняя оставшиеся инструкции. Что это значит? Например, у нас есть задача сложить все числа от нуля до девяти, и при этом сложить только четные числа. С помощью `continue` мы можем реализовать это вот так, как вы видите на примере, то есть тогда, когда переменная `i` является нечетным числом, мы пишем `continue` и переходим к следующей итерации цикла `for`. Тем самым в `result` суммируются только четные числа, и мы получаем правильный ответ." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "20\n" + ] + } + ], + "source": [ + "result = 0\n", + "\n", + "for i in range(10):\n", + " if i % 2 != 0:\n", + " continue\n", + " result += i\n", + "\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Выберите варианты, в которых цикл будет работать бесконечно:\n", + "\n", + "- [x] Вариант первый:\n", + "\n", + "```python\n", + "while True:\n", + " continue\n", + "```\n", + "\n", + "- [x] Вариант второй:\n", + "\n", + "```python\n", + "while True:\n", + " pass\n", + "```\n", + "\n", + "- [ ] Вариант третий:\n", + "\n", + "```python\n", + "while True:\n", + " break\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Так же для `for` и `while` есть `else` который срабатывает по окончанию, но не сработает если был использован `break`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "while\n", + "else\n" + ] + } + ], + "source": [ + "flag = True\n", + "\n", + "while flag:\n", + " print(\"while\")\n", + " flag = False\n", + " \n", + "else:\n", + " print(\"else\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "while\n" + ] + } + ], + "source": [ + "flag = True\n", + "\n", + "while flag:\n", + " print(\"while\")\n", + " break\n", + " \n", + "else:\n", + " print(\"else\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "for\n", + "else\n" + ] + } + ], + "source": [ + "for i in range(1):\n", + " print(\"for\")\n", + " \n", + "else:\n", + " print(\"else\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "for\n" + ] + } + ], + "source": [ + "for i in range(1):\n", + " print(\"for\")\n", + " break\n", + " \n", + "else:\n", + " print(\"else\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь, когда мы рассмотрели все основные конструкции управления потоком, мы можем попробовать применить их на практике и написать небольшую программу, которая будет содержать их в себе. Давайте это сделаем." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/3. Базовые типы и конструкции/Логический тип.ipynb b/1. Введение в Python/3. Базовые типы и конструкции/Логический тип.ipynb new file mode 100644 index 0000000..b8ad992 --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/Логический тип.ipynb @@ -0,0 +1,533 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Базовые типы: логический тип #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На этой лекции мы поговорим о ещё одном базовом типе, который есть в Python, а именно о логическом типе, он же тип `bool`.\n", + "\n", + "В Python'е тип `bool` представлен двумя значениями, как и в других языках — это `True` и это `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "True" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Что важно отметить, что в Python тип `bool` является подтипом целого числа, и, по большому счету `True` — это `True` соответствует единице, а `False` соответствует нулю. Если мы присвоим переменной `result` `True`, то, посмотрев на тип переменной, мы увидим, что это как раз тип `bool`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "result = True\n", + "print(type(result))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Когда нужны логические типы? По большому счету они нужны тогда, когда нужно проверить на истинность какое-то выражение — является выражение истинным либо является выражение ложью.\n", + "\n", + "Например, в Python'е мы можем проверить, что значения двух объектов равны. Это делается с помощью оператора «равно». Это два знака равно, идущие друг за другом." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "13 == 13" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также есть оператор «не равно», который позволяет сделать противоположное действие, то есть сравнить то, что значения не равны. Обратите внимание, что результатом выполнения обоих выражений являются логические типы — либо `True`, либо `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 != 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Операторы сравнения ##\n", + " - `>` больше;\n", + " - `>=` больше или равно;\n", + " - `<` меньше;\n", + " - `<=` меньше или равно." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n", + "True\n", + "True\n", + "False\n" + ] + } + ], + "source": [ + "print(3 > 3)\n", + "print(3 >= 3)\n", + "print(6 < 7)\n", + "print(6 <= 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "И также интересной особенностью является наличие в Python'е множественного сравнения." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "x = 2\n", + "print(1 < x < 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Мы объявляем переменную `x`, которая равна целому числу 2. Дальше записываем множественное сравнение, которое в итоге выдает результат `True`, потому что `x > 1`, и в то же время `x < 3`. Это очень удобно бывает на практике." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Конвертация типов ##\n", + "\n", + "Если мы попробуем преобразовать целое число к типу `bool`, то есть к логическому типу, то если число не равняется нулю, то получим в результате `True`, если 0, то `False`. То же самое верно для вещественных чисел." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(12)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(-4)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(.5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Поговорим о логических выражениях.\n", + "\n", + "Логические выражения — это выражения, которые содержат в себе один или больше логических операторов. Какие мы знаем логические операторы? Это может быть логическое «и», логическое «или» или, например, логическое отрицание.\n", + "\n", + "В Python'е представлены все эти операторы и, например, логическое «и» записывается с помощью слова `and`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n" + ] + } + ], + "source": [ + "x, y = True, False\n", + "print(x and y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также есть логическое «или» — это оператор `or`. И есть логическое отрицание — это оператор `not`." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "x, y = True, False\n", + "print(x or y)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "y = False\n", + "print(not y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Мы можем составлять сложные логические выражения, в которые будут входить несколько логических операторов. При этом в этом логическом выражении у операторов есть свой приоритет, порядок исполнения, как и в математических операциях. Стоит отметить, что этот порядок мы также можем задавать, используя круглые скобки, как и в случае с математическим выражением." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "x, y, z = True, False, True\n", + "result = x and y or z\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Следующая особенность Python — это то, что логические выражения ленивые. Давайте подробно разберем это на примере. У нас есть переменная `x`, которая равняется 12 и переменная `y`, которая равняется `False`. И есть логическое выражение `x or y`. В результате работы этого выражения мы видим, что на экране не `True`, как мы могли бы ожидать, а число 12." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12\n" + ] + } + ], + "source": [ + "x = 12\n", + "y = False\n", + "\n", + "print(x or y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Что здесь происходит? Python начинает интерпретировать логическое выражение, видит, что `x` является истинным, и понимает, что ему не нужно выполнять оставшуюся часть логического выражения. Поэтому он y уже не будет проверять, `x` — это истина, оператор `or` стоит, значит нас устраивает то, что мы можем остановиться в этот момент и результатом выполнения выражения будет как раз значение `x`. На другом примере у нас есть переменная `x`, которая равна 12, и переменная `z`, которая равна строке `boom`. В результате работы логического выражения `x and z` мы получаем как результат работы логического выражения строку `boom`." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "boom\n" + ] + } + ], + "source": [ + "x = 12\n", + "z = \"boom\"\n", + "\n", + "print(x and z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Происходит все то же самое. Python выполняет логическое выражение, до тех пор, пока оно имеет смысл, и результатом является последнее значение.\n", + "\n", + "Посмотрим небольшой пример — задачу, определить високосный год или нет. Существует такое правило, которое позволяет понять, является ли год високосным или нет. Год является високосным, если он кратен 4, но при этом не кратен 100, либо кратен 400. Звучит достаточно запутанно, однако решение всего в три строчки и как раз с помощью логических выражений. Мы объявляем переменную, которая содержит в себе год и далее составляем сложное логическое выражение, включающее в себя несколько логических операторов. При этом задаем порядок выполнения операторов скобками, обратите внимание. И что стоит отметить — то, что это то, о чем я говорил. Если год не кратен четырем, то есть первое выражение не является истинным, то логическое выражение не будет выполняться дальше, Python в этот момент остановится. Если же оно истинное, то выполнение логического выражения продолжится. Ну и мы получаем ответ. В данном случае 2017 год не является високосным." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n" + ] + } + ], + "source": [ + "year = 2017\n", + "\n", + "is_leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n", + "\n", + "print(is_leap)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Что стоит отметить — это то, что эту задачку мы могли бы решить ещё короче, всего в две строчки, используя модуль стандартной библиотеки `calendar`. Как принято говорить, Python — это «батарейки включены», то есть это язык, который предоставляет очень много возможностей в своей стандартной библиотеке. Так что библиотеку языка ставят в пример многим другим языкам программирования. Соответственно, есть модуль «календарь», который мы можем импортировать, и внутри модуля календарь есть функция `isleap`, которая делает как раз то, что нам нужно. Мы, например, можем убедиться, что 1980-й год был високосным. Про импорты мы ещё с вами будем говорить в дальнейшем." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "from calendar import isleap\n", + "\n", + "print(isleap(1980))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На этой лекции мы поговорили про логический тип в Python, тип `bool`, рассмотрели логические операторы, а также посмотрели на составные логические выражения." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/3. Базовые типы и конструкции/Объект None.ipynb b/1. Введение в Python/3. Базовые типы и конструкции/Объект None.ipynb new file mode 100644 index 0000000..0323193 --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/Объект None.ipynb @@ -0,0 +1,178 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Базовые типы: объект None #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Еще один важный объект, который стоит рассмотреть, это объект `None`.\n", + "\n", + "Объект `None` по большому счету является единственным значением специального типа `NoneType`, который используется для того, чтобы подчеркнуть отсутствие значения. Если вы знаете язык С, то вы можете рассматривать `None` как эквивалент нулевому указателю.\n", + "\n", + "В программах на Python'е вы будете часто видеть `None` как в чужом коде, так и будете использовать его в своем. Для того чтобы присвоить переменной `None`, мы можем написать просто `answer = None`. Ничего нового для вас в этом нет. Если мы посмотрим на тип `None`, то мы увидим, что это как раз класс `NoneType`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "answer = None\n", + "print(type(None))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Однако обратите внимание, что с классом `NoneType` вам работать не придется, по большому счету вы работаете только с единственным значением, которое представляет собой объект `None`. Важно отметить, что при преобразовании `None` к типу `bool` всегда получается `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(None)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Поэтому если переменная равна `None` и мы используем условный оператор, `if not answer` всегда будет выполняться, потому что `if not answer` будет всегда `True`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ответ не получен\n" + ] + } + ], + "source": [ + "answer = None\n", + "\n", + "if not answer:\n", + " print(\"Ответ не получен\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте посмотрим на примере, когда `None` может потребоваться в коде. Предположим, у нас есть программа, в которой мы рассчитываем какой-то доход, доход от продаж чего-либо, неважно чего. Предположим, у нас доход, который лежит в переменной `income`, равен нулю. Тогда в коде мы могли бы написать `if not income` и напечатать на экран \"Ничего не заработали\". Если у нас доход ноль, то мы ничего не заработали." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ничего не заработали\n" + ] + } + ], + "source": [ + "income = 0\n", + "\n", + "if not income:\n", + " print(\"Ничего не заработали\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Однако предположим, что нам нужно подчеркнуть такую ситуацию, что мы еще даже не начинали продавать. Конечно же, до начала продаж доход всегда будет равен нулю. В этот момент может пригодится `None`. Мы присваиваем переменной `income` значение `None`, и после этого в условиях сравнения мы можем проверять, что если `income` является объектом `None`, то тогда печатаем на экран что мы еще даже не начинали продавать. Иначе — `elif not income`, — иначе если у нас в переменной `income` ноль, то мы и печатаем доход ноль, мы ничего не заработали. В данном случае важно отметить, что мы проверяем равенство переменной `income` объекту `None` с помощью оператора `is`. Это идиоматичный способ проверки соответствия переменной объекту `None` в Python'е. Мы могли бы, конечно, написать `if income == None`, но это неидиоматично в Python, потому что в Python'е оператор `==` можно переопределить с помощью магических методов, и мы это увидим в дальнейшем, что такое магические методы у классов." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Еще не начинали продавать\n" + ] + } + ], + "source": [ + "income = None\n", + "\n", + "if income is None:\n", + " print(\"Еще не начинали продавать\")\n", + "elif not income:\n", + " print(\"Ничего не заработали\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Что еще можно сделать с `None`? `None` будет часто использоваться как значение именованного аргумента в функции, для того чтобы подчеркнуть, что аргумент функции является опциональным. Также значение `None` присваивается при инициализации классом всевозможным атрибутам, которые в дальнейшем могут быть перезаписаны каким-то полезным значением. Также `None` по умолчанию возвращается из функции, если вы самостоятельно явным образом ничего из функции не вернули с помощью конструкции `return`. Все эти слова про функции и про классы могут звучать для вас по-новому, но мы про все это расскажем в следующих лекциях нашего курса. Ну а пока вы познакомились с объектом `None`, который теперь вас не испугает, когда вы его увидите." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/3. Базовые типы и конструкции/Пример на управление потоком.ipynb b/1. Введение в Python/3. Базовые типы и конструкции/Пример на управление потоком.ipynb new file mode 100644 index 0000000..520e622 --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/Пример на управление потоком.ipynb @@ -0,0 +1,371 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Пример на управление потоком #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На этой лекции мы напишем небольшую программу как пример на конструкции управления потоком.\n", + "\n", + "Программа будет представлять собой простую игру, в которой пользователю нужно угадать загаданное число. Я уже написал такую программу. Давайте посмотрим на неё в действии. Запустим." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "^C\n" + ] + } + ], + "source": [ + "! python main.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Программа предлагает ввести число. Когда мы вводим, на экран выводится подсказка о том, загаданное число больше либо меньше того, что мы ввели. Если мы число угадываем, то на экран печатается \"Совершенно верно\", и программа завершает свою работу. Давайте напишем аналогичную программу." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Писать код мы будем в среде разработки `PyCharm Community Edition`. Давайте запустим `PyCharm`. И пока он запускается, стоит отметить, что подробно процесс установки `PyCharm` системы мы описали в сопроводительных документах. Когда `PyCharm` загрузился, нам нужно нажать кнопочку `New Project (Новый Проект).`. Далее `PyCharm` предлагает заполнить два текстовых поля. В первом нужно указать путь на жёстком диске до проекта, который мы создаём. Давайте назовём папочку `game`. Второе текстовое поле — это путь до интерпретатора Python 3 в системе. В данном случае `PyCharm` автоматически определил правильный путь до интерпретатора, который установлен у меня, поэтому всё, что осталось нажать — кнопочку `Create` которая создаёт новый проект. Как только проект открылся, нам нужно открыть в нём Python'овский файл `main.py`. И откроется окно редактора, в котором мы уже можем писать код, удалив код по умолчанию." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте начнём. Первое, что мы сделаем, это создадим переменную `number`, которой присвоим число 42. И это то число, которое нужно отгадать." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "number = 42" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Далее мы воспользуемся бесконечным циклом `while True`, который будет выполняться до тех пор, пока мы из него явным образом не выйдем. Внутри этого цикла на каждой итерации нам нужно получать ввод пользователя из терминала. Мы можем это сделать с помощью встроенной функции `input`. Внутри функции `input` мы будем просить пользователя ввести число. Если пользователь ввёл пустую строку, то есть просто нажал Enter, либо напечатал слово `exit`, мы должны выйти из программы. То есть если строка пустая, `if not answer`, или строка равна строке `exit`, мы выходим. Чтобы выйти из бесконечного цикла, воспользуемся оператором `break`. При этом программа завершится." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: exit\n" + ] + } + ], + "source": [ + "while True:\n", + " answer = input(\"Введите число: \")\n", + " \n", + " if not answer or answer == \"exit\":\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Далее, у нас есть строковый ввод пользователя. Давайте проверим на то, что этот ввод преобразовывается к целому числу. Для этого воспользуемся методом строки `isdigit`. Если ввод пользователя не преобразовывается к целому числу, то напечатаем на экран: \"Введите правильное число\". И воспользуемся оператором `continue` для того, чтобы перейти к новой итерации цикла `while`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: bla bla bla\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Введите правильное число!\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: exit\n" + ] + } + ], + "source": [ + "while True:\n", + " answer = input(\"Введите число: \")\n", + " \n", + " if not answer or answer == \"exit\":\n", + " break\n", + " \n", + " if not answer.isdigit():\n", + " print(\"Введите правильное число!\")\n", + " continue" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Далее, мы знаем теперь, что ввод пользователя преобразовывается к целому числу. Давайте преобразуем." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: 8\n", + "Введите число: exit\n" + ] + } + ], + "source": [ + "while True:\n", + " answer = input(\"Введите число: \")\n", + " \n", + " if not answer or answer == \"exit\":\n", + " break\n", + " \n", + " if not answer.isdigit():\n", + " print(\"Введите правильное число!\")\n", + " continue\n", + " \n", + " user_answer = int(answer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь, когда переменная `user_answer` содержит целое число, мы можем сравнивать его с загаданным числом. Если `user_answer` больше, чем загаданное число, то печатаем на экран фразу \"Загаданное число меньше\". Если `user_answer` меньше, чем загаданное число, тогда печатаем на экран фразу \"Загаданное число больше\". Иначе, блок `else` будет выполнен тогда, когда пользователь угадал число. Мы можем его поздравить, написав \"Совершенно верно!\", и выйти из программы с помощью оператора `break`. Вот, собственно, и всё. Мы написали программу аналогичную той, что я показывал в начале видео." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: 10\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Загаданное число больше\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: 50\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Загаданное число меньше\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: 42\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Совершенно верно!\n" + ] + } + ], + "source": [ + "while True:\n", + " answer = input(\"Введите число: \")\n", + " \n", + " if not answer or answer == \"exit\":\n", + " break\n", + " \n", + " if not answer.isdigit():\n", + " print(\"Введите правильное число!\")\n", + " continue\n", + " \n", + " user_answer = int(answer)\n", + " \n", + " if user_answer > number:\n", + " print(\"Загаданное число меньше\")\n", + " elif user_answer < number:\n", + " print(\"Загаданное число больше\")\n", + " else:\n", + " print(\"Совершенно верно!\")\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Однако вы можете сказать, что это игра неинтересная, потому что ответ заранее известен. Давайте это исправим. Мы воспользуемся модулем стандартной библиотеки `random` для того, чтобы генерировать на старте программы случайное число в промежутке от 0 до 100. Про импорты мы ещё будем говорить в дальнейшем, ну а пока просто воспользуемся для нашего примера. Внутри модуля `random` стандартной библиотеки есть функция `randint`, которая возвращает число в каком-то промежутке, целое. Теперь в нашу программу интересно играть даже нам самим." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "from random import randint\n", + "\n", + "number = randint(0, 101)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: 40\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Загаданное число больше\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: 60\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Загаданное число меньше\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Введите число: 59\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Совершенно верно!\n" + ] + } + ], + "source": [ + "while True:\n", + " answer = input(\"Введите число: \")\n", + " \n", + " if not answer or answer == \"exit\":\n", + " break\n", + " \n", + " if not answer.isdigit():\n", + " print(\"Введите правильное число!\")\n", + " continue\n", + " \n", + " user_answer = int(answer)\n", + " \n", + " if user_answer > number:\n", + " print(\"Загаданное число меньше\")\n", + " elif user_answer < number:\n", + " print(\"Загаданное число больше\")\n", + " else:\n", + " print(\"Совершенно верно!\")\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Чтобы запустить код, мы нажимаем правой кнопкой на белой области редактора и выбираем пункт меню `Run`. `PyCharm` запускает встроенный терминал, и, в принципе, мы можем пользоваться нашей программой как раз из этого терминала. Однако здесь написано, как `PyCharm` запускает программу, которую мы только что написали. Давайте запустим её самостоятельно из терминала. Введите число. Давайте попробуем для начала ввести что-то неправильное. Введите правильное число. А теперь попробуем отгадать число.\n", + "\n", + "Мы написали программу, игру, как пример на конструкции управления потоком." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/3. Базовые типы и конструкции/Строки и байтовые строки.ipynb b/1. Введение в Python/3. Базовые типы и конструкции/Строки и байтовые строки.ipynb new file mode 100644 index 0000000..b57be42 --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/Строки и байтовые строки.ipynb @@ -0,0 +1,1101 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Базовые типы: строки и байтовые строки #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На этой лекции мы с вами продолжаем знакомство с базовыми типами и сейчас мы рассмотрим такой тип как строки и байтовые строки. Начнем мы со строк.\n", + "\n", + "Строка в Python это неизменяемая последовательность unicod'ных символов. Давайте посмотрим на примере. Чтобы определить строку, всё что нам нужно сделать, это записать её и обрамить в двойные кавычки." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "да будет слово ваше\n" + ] + } + ], + "source": [ + "example = \"да будет слово ваше\"\n", + "print(example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Убедимся что тип, объект, который мы только что с вами создали это str, то есть строка. Опять же, мы пользуемся встроенной функцией type для этого. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(type(\"слово\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Иногда может получится так, что вам внутри строки может записать слова которые включают в себя двойные кавычки. В таком случае вы можете воспользоватся одинарными кавычками для того, чтобы записать строку." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "да будет \"слово\" ваше\n" + ] + } + ], + "source": [ + "example = 'да будет \"слово\" ваше'\n", + "print(example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также можно двойные кавычки экранировать символом обратного слэша и таким образом мы можем двойные кавычки записывать внутри строки, которая создается с помощью двойных кавычек." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "да будет \"слово\" ваше\n" + ] + } + ], + "source": [ + "example = \"да будет \\\"слово\\\" ваше\"\n", + "print(example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также в Python'е есть концепция сырых строк. Это строки, которые начинаются с английской буквы `r`. Посмотрите на пример. Мы можем с помощью сырых строк объявить строку и не экранировать специальные символы, которые находятся внутри этой строки. В первом случаи мы экранируем обратные слэши обратными слэшами и это выглядит достаточно некрасиво. Во втором случае мы используем сырые строки и просто записываем наш путь на диске." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "C:\\Windows\n" + ] + } + ], + "source": [ + "path = \"C:\\\\Windows\"\n", + "print(path)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "C:\\Windows\n" + ] + } + ], + "source": [ + "path = r\"C:\\Windows\"\n", + "print(path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Разбить длинную строку внутри вашего кода на несколько строк можно с помощью обратного слэша. Вы записываете строку, ставите обратный слэш и переносите строчку на следующую строку кода, как видно на примере." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Perl - это тот язык, который одинаково выглядит как до, так и после RSA шифрования. Кейт Бостик\n" + ] + } + ], + "source": [ + "example = \"Perl - это тот язык, который одинаково \" \\\n", + " \"выглядит как до, так и после RSA шифрования. \" \\\n", + " \"Кейт Бостик\"\n", + "\n", + "print(example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также есть возможность записывать строки, состоящие из нескольких абзацов, какой-то текст возможно. Это делается с помощью тройных кавычек." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Есть всего два типа языков программирования: те, на которые\n", + "люди всё время ругаются, и те, которые никто не использует.\n", + "\n", + "Бьерн Страуструп\n", + "\n" + ] + } + ], + "source": [ + "example = \"\"\"\n", + "Есть всего два типа языков программирования: те, на которые\n", + "люди всё время ругаются, и те, которые никто не использует.\n", + "\n", + "Бьерн Страуструп\n", + "\"\"\"\n", + "\n", + "print(example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Как объединить две строки в одну? Иногда вам требуется объединить две строки. Это делается просто с помощью оператора сложения, с помощью знака \"+\" и в итоге получается новая строка." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Можно просто складывать строки'" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"Можно просто \" + \"складывать строки\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Когда вы складываете много строчек, это может быть не еффективно, потому что строки неизменяемые и при сложении нескольких строк на каждое сложение создается новый объект строковый и создается новая локация памяти. Однако в самом простейшем случае вы можете просто сложить две строки и это будет решением множества практических задач. Строки можно даже умножать, потому что для них переопределен оператор умножения. Если вы умножите строчку на три, например, то получите ту же самую строчку, повторяющуюся три раза." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "бла бла бла \n" + ] + } + ], + "source": [ + "print(\"бла \" * 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Я сказал, что строки неизменяемы. Давайте посмотрим на это подробно. На примере на слайде мы создаем строку. Дальше мы печатаем её адрес в памяти. Адрес памяти у объекта в Python можно запросить с помощью встроеной функции `id`." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1583375738000\n", + "1583375738336\n" + ] + } + ], + "source": [ + "example = \"Привет\"\n", + "print(id(example))\n", + "example += \", Мир!\"\n", + "print(id(example))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Далее мы к строке добавляем ещё одну строчку, чтобы у нас получилась строка \"Привет мир\" и печатаем адрес получившегося строкового объекта. Как мы видим, эти адреса отличаются. То есть когда мы изменили нашу начальную строку, мы на самом деле создали новый строковый объект. Обратите внимание, что я говорю строковый объект. Про объектную структуру в Python мы с вами будем говорить в отдельном видео. Я по ходу наших лекций, стараюсь их подчеркнуть, что всё в Python является объектом. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Следующая особенность это срезы строк. Срезы строк очень удобны для того, чтобы из целой строки выделить какую-то подстроку. Синтаксис срезов лучше всего посмотреть на примере. Давайте посмотрим пример, это всё та же строка \"Привет, Мир!\"." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Далее мы берем от нее срез. Срез это - квадратные скобки и внутри три параметра, start, stop, step. То есть, начиная с какого символа мы хотим получить подстроку, до какого символа и какой шаг. На примере мы видим, что мы берем из строки срез начиная с восьмого символа и до конца. Отсутствие параметров stop, step говорит как раз, что нам нужно брать срез до конца." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Мир!'" + ] + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example = \"Привет, Мир!\"\n", + "example[8:]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На втором примере мы берем срез с 8 по 11 символы." + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Мир'" + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example[8:11]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также в срезе можно использовать отрицательные значения. Если вы посмотрите на пример, где берется срез -4:. Это означает что срез начнется с четвертого символа с конца строки, дальше двоеточие и до конца строки. Мы в итоге получаем слово \"Мир!\" из нашей строки." + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Мир!'" + ] + }, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example[-4:]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Использование step. Использование шага может быть оправдано тогда, когда вам нужно получить только символы из строки с определенным шагом. Давайте посмотрим на пример. У нас есть строка, которая состоит из цифр от 0 до 9, и мы хотим получить подстроку, которая состоит из четных чисел. В данном случае step нам очень поможет и если мы возьмем срез с шагом 2, то получим то, что мы хотим. Обычно шаг используется на практике для того, чтобы инвертировать строку, то есть развернуть её в обратном направлении. Например, у нас есть строка \"Москва\" и мы берем срез:-1, то есть шаг = -1, то мы получаем строку развернутую." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'02468'" + ] + }, + "execution_count": 86, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example = \"0123456789\"\n", + "example[::2]" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'авксоМ'" + ] + }, + "execution_count": 87, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example = \"Москва\"\n", + "example[::-1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Так как Python всё есть объект, у строк есть методы. Что это значит? Если мы определим строку и присвоим её переменой, то у этой строки мы потом можем вызывать различные методы. Например на примере мы видим вызов метода сount строки, который позволяет найти количество вхождений буквы \"о\" в строку." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 89, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "torvalds = \"\"\"\n", + "Болтовня ничего не стоит. Покажите мне код\n", + "Linus Torvalds\n", + "\"\"\"\n", + "\n", + "torvalds.count(\"о\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте посмотрим на другой метод, который называется capitalize. Этот метод позволяет сделать первую букву в строке заглавной." + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Москва'" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example = \"москва\"\n", + "example.capitalize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ну и последнее, на что мы посмотрим, это isdigit. Isdigit позволяет проверить является ли строка, у которой мы вызвали этот метод, числом и возвращает boolean - True или False." + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"2021\".isdigit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На самом деле существует ещё масса методов у строк, которые вы можете посмотреть в документации и с которыми вы столкнетесь на практике и в том числе в рамках нашего курса. Мы ещё будем пользоватся методами строк активно. https://docs.python.org/3/library/string.html\n", + "\n", + "Давайте пойдем дальше и посмотрим на оператор in, который позволяет проверить наличие подстроки в строке. Например, мы можем записать вот такую вещь и проверить, что 3.14 содержится в строке, которая написана на экране. В данном случае она содержится, поэтому результат True, то есть истина." + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"3.14\" in \"14 марта отметят День π: если записать дату на американский манер, получится 3.14. Точное значение числа вычислить невозможно, поэтому у него тянется громадный хвост цифр.\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Если мы проверим наличие подстроки, которая не содержится в строке, мы получаем False. Это очень читаемо, это очень удобно и это идиоматично." + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 98, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"Rossum\" in \"Гвидо ван Россум\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также есть оператор `for…in`. Выражение `for…in`, которое позволяет итерироватся по строке. Как мы знаем, строка это последовательность. Последовательность юникодных символов. Соответственно итерация по строке означает взятие каждого следующего элемента по очереди. Давайте посмотрим как это сделать. Если мы объявим строковый объект изначальный \"привет\" и дальше проитерируемся по этой строке, используя выражение `for…in`, то мы можем получить каждый отдельный символ этой строки - букву П, букву Р, букву И и т.д." + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Буква п\n", + "Буква р\n", + "Буква и\n", + "Буква в\n", + "Буква е\n", + "Буква т\n" + ] + } + ], + "source": [ + "example = \"привет\"\n", + "\n", + "for letter in example:\n", + " print(\"Буква\", letter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Конвертация типов. Давайте посмотрим, что у нас можно сделать со строкой, например с классом `str`. Например, у нас есть вещественное число, и мы можем в runtime, в процесе работы Python применить конвертацию типов и преобразовать вещественное число в число типа `str`. И на примере мы это видим. Мы берем `999.01`, преобразовываем в строку и проверяем, что у нас действительно получилась в результате конвертации типов строка. " + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/plain": [ + "'999.01'" + ] + }, + "execution_count": 102, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "string = str(999.01)\n", + "\n", + "print(type(string))\n", + "string" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Если мы попробуем преобразовать не пустую строку к логическому типу, то мы получим `True`. Пустая строка, которая не содержит ни одного символа в себе, при конвертации к логическому типу возвращает `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 103, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(\"Строка\")" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 104, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Очень часто на практике может возникнуть задача форматирования строк. Что такое \"форматирование\"? Форматирование строк - это процесс, когда у вас есть какой-то шаблон строковый, внутрь которого вместо определенных placeholder-ов вы хотите подставить те или иные значения из ваших переменных, например. Давайте посмотрим на первый способ форматирования в Python, которых достаточно много, на самом деле. Первый способ это вот такой, как вы видите на примере, мы placeholder-ы определяем с помощью синтаксиса %s. Это означает, что вместо этого placeholder-а мы можем подставить строку. Если бы мы хотели подставить вместо него целое число, то это был бы %d. И дальше, с помощью оператора % мы можем заменить эти placeholder-ы на наши значения. На самом деле, это достаточно богатая спецификация всевозможных placeholder-ов и про все можно почитать в документации." + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Лень — главное достоинство программиста. Larry Wall'" + ] + }, + "execution_count": 105, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "template = \"%s — главное достоинство программиста. %s\"\n", + "template % (\"Лень\", \"Larry Wall\")" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'14 марта отметят День π: если записать дату на американский манер, получится 3.14.'" + ] + }, + "execution_count": 113, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "template = \"%d марта отметят День π: если записать дату на американский манер, получится %.2f.\"\n", + "template % (14, 3.14)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Мы давайте посмотрим на другой чуть более удобный способ, на мой взгляд, форматирования строк - это использование метода `format`, который есть у строки. В данном случае placeholder-ом являются фигурные скобки. Мы пишем строку, которая включает в себя placeholder-ы, а дальше используем метод `format`, которому передаем значения, которые мы должны подставить вместо placeholder-ов. В данном случае нам необязательно указывать тип внутри наших placeholder-ов, Python все это разрешит сам." + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Цифры не лгут, но лжецы пользуются формулами. (Роберт Хайнлайн)'" + ] + }, + "execution_count": 110, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"{} не лгут, но {} пользуются формулами. ({})\".format(\n", + " \"Цифры\", \"лжецы\", \"Роберт Хайнлайн\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Другой способ, чуть более наглядный, опять же с использованием функции `format`, но с именованными аргументами. В данном случае, у нас внутри placeholder-а содержится имя этих placeholder-ов, а в функцию `format` мы передаем, соответственно, именованные аргументы по имени, совпадающему с именем placeholder-а и получаем итоговую строку." + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'640 Кб должно быть достаточно для каждого. (Билл Гейтс)'" + ] + }, + "execution_count": 111, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"{num} Кб должно быть достаточно для каждого. ({author})\".format(\n", + " num=640, author=\"Билл Гейтс\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Но, пожалуй, самый удобный способ форматирования строк в Python появился в Питоне 3.6. Это так называемые f-строки. Это строки, которые начинаются с префикса английской буквы f. Посмотрите на пример - мы можем просто объявить две переменные, а затем записать f-строку, в которой внутри placeholder-ов просто написать имена этих переменных. И вместо этих placeholder-ов будут подставлены правильные значения." + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Преждевременная оптимизация — корень всех зол. (Дональд Кнут)'" + ] + }, + "execution_count": 115, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subject = \"оптимизация\"\n", + "author = \"Дональд Кнут\"\n", + "\n", + "f\"Преждевременная {subject} — корень всех зол. ({author})\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также есть возможность использовать модификаторы форматирования. Модификаторы форматирования позволяют вам сказать языку, как вы хотите то или иное значение в placeholder-е подставлять, в каком виде его в итоге вывести. Опять же, проще всего посмотреть на примере. Если у нас есть число и мы хотим вывести его в итоге на экран в двоичном представлении, мы можем воспользоваться модификатором форматирования `:#b`. И в итоге получаем на экране число в двоичном представлении." + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Bin: 0b1000'" + ] + }, + "execution_count": 117, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num = 8\n", + "f\"Bin: {num:#b}\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Другой распространенный пример, когда нам нужен модификатор форматирования, это, например, деление, которое возвращает число с очень длинной десятичной частью. Например, 2/3, мы знаем, что это будет 0.(6). Если мы хотим вывести только три знака после точки, мы можем воспользоваться модификатором форматирования `:.3f`." + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.6666666666666666\n", + "0.667\n" + ] + } + ], + "source": [ + "num = 2/3\n", + "print(num)\n", + "\n", + "print(f\"{num:.3f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Опять же, модификаторов форматирования очень много и вы можете почитать про них подробно в документации. https://docs.python.org/3/library/string.html#format-specification-mini-language" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Встроенная функция input позволяет получить ввод пользователя в виде строки. Пожалуй, лучше всего это показать на интерактивном примере в консоли. Давайте мы это сейчас и сделаем. В консоли мы запустим Python 3 `name = input(\"введите ваше имя\")`. После этого нам предлагается ввести наше имя. Давайте его введем. И после того, как мы ввели имя, нажали `enter`, внутри переменной `name` содержится как раз то значение, которое мы ввели, и мы можем с ним работать." + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "введите ваше имя Саша\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Привет, Саша!\n" + ] + } + ], + "source": [ + "name = input(\"введите ваше имя\")\n", + "print(f\"Привет, {name}!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Следующее, на что мы посмотрим, это новый тип, это байтовые строки, тип `bytes`.\n", + "\n", + "Байты, как вы знаете, это минимальная единица хранения и передачи информации. Мы не можем, например, передавать по сети строки в виде юникодных символов. Нам нужно преобразовывать строки сначала в байты. То же самое, если мы хотим сохранить строку на диск, то диск работает с байтами. Нам нужно преобразовать наши юникодные символы в байты для этой операции. В Python для того, чтобы объявить байтовую строку используется литерал `b`. Давайте посмотрим на примере. Чтобы объявить байтовую строку мы используем литерал `b` и передаем в нее строчку \"hallo\". В данном случае мы получаем байтовую строку и она представляет собой последовательность чисел от 0 до 255. У нас получилось это сделать, потому что мы передали в литерал байтовой строки ASCII символы. То есть, символы, которые имеют свой номер в таблице ASCII от 0 до 255 и, соответственно, преобразовываются в байты очень легко. И давайте удостоверимся, что мы действительно получили байтовую строку. " + ] + }, + { + "cell_type": "code", + "execution_count": 123, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "example = b\"hello\"\n", + "print(type(example))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Проитерируемся по нашей байтовой строке. Так как она является последовательностью, то мы можем по ней итерироваться и мы это уже умеем делать, на примере строк было показано. И мы видим, что наша байтовая строка содержит числа от 0 до 255, однако, если мы попробуем преобразовать в байты нашу юникодную строку, которая, в данном случае \"привет\", мы получим ошибку." + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "104\n", + "101\n", + "108\n", + "108\n", + "111\n" + ] + } + ], + "source": [ + "for element in example:\n", + " print(element)" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "metadata": {}, + "outputs": [ + { + "ename": "SyntaxError", + "evalue": "bytes can only contain ASCII literal characters. (, line 1)", + "output_type": "error", + "traceback": [ + "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m1\u001b[0m\n\u001b[1;33m example = b\"Привет\"\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m bytes can only contain ASCII literal characters.\n" + ] + } + ], + "source": [ + "example = b\"Привет\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Как раз потому, что байты - это числа от 0 до 255, и нам нужно юникодные символы преобразовать в байты. Для этого используются кодировки. Самая известная кодировка - это UTF-8, поэтому мы сейчас на примере покажем, как обычную строку, в данном случае строку \"привет\", преобразовать в байты, используя кодировку UTF-8. Давайте создадим строковый объект, убедимся, что это строка. А дальше воспользуемся методом `encode`, который есть у строки. Encode принимает опциональным параметром кодировку, в которую мы хотим закодировать нашу строку. В данном случае UTF-8. В принципе, это кодировка по умолчанию, поэтому мы могли бы просто использовать метод `encode` без каких-то дополнительных аргументов. В результате мы видим, что мы получаем байтовую строку, то есть, последовательность байт." + ] + }, + { + "cell_type": "code", + "execution_count": 129, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "b'\\xd0\\xbf\\xd1\\x80\\xd0\\xb8\\xd0\\xb2\\xd0\\xb5\\xd1\\x82'\n", + "\n" + ] + } + ], + "source": [ + "example = \"привет\".encode(encoding=\"utf-8\")\n", + "print(example)\n", + "print(type(example))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте посмотрим. Python напечатал на экран байтовую строку в виде последовательности шестнадцатиричных символов. Вначале мы видим символы `\\xd0\\xbf`. Что это такое? На самом деле, это представление маленькой кириллической буквы \"п\" в кодировке UTF.\n", + "\n", + "| Буква | Кодировка | hex | dec (bytes) | dec | binary |\n", + "|:------|:----------|:------|:------------|:------|:------------------|\n", + "| п | UTF-8 | D0 BF | 208 191 | 53439 | 11010000 10111111 |" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Если мы посмотрим в шестнадцатиричном представлении как выглядит буква \"п\" в кодировке UTF, мы как раз видим эти шестнадцатиричные символы `\\xd0 \\xbf`. То есть, ничего магического. То же самое с другими буквами. Чтобы декодировать байты обратно в строку, мы можем воспользоваться методом строки, методом `decode`, который есть у байтов. Вызываем метод decode и получаем обратно UTF строку. Decode может принимать параметром кодировку, которую мы будем использовать. Но, так как мы работаем в данном случае с `UTF-8`, это значение по умолчанию, мы просто пишем `decode` без каких-то аргументов." + ] + }, + { + "cell_type": "code", + "execution_count": 132, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'привет'" + ] + }, + "execution_count": 132, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example.decode()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На этой лекции мы научились работать со строками, посмотрели на основные операции, которые есть у нас для работы со строками, а также посмотрели на байтовые строки, на тип bytes. Вам на практике часто придется с ними сталкиваться." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/3. Базовые типы и конструкции/Тест на типы и конструкции (Clear).ipynb b/1. Введение в Python/3. Базовые типы и конструкции/Тест на типы и конструкции (Clear).ipynb new file mode 100644 index 0000000..9c5c70c --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/Тест на типы и конструкции (Clear).ipynb @@ -0,0 +1,270 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Тест на типы и конструкции #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 1. 10 / 2 – что получится в результате деления?\n", + "\n", + "- [ ] Вещественное число 5.0\n", + "- [ ] Целое число 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 2. True или False?\n", + "\n", + "```python\n", + "bool(0.0)\n", + "```\n", + "\n", + "- [ ] True\n", + "- [ ] False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 3. Чему равно name?\n", + "\n", + "```python\n", + "x = 0\n", + "y = 12\n", + "name = x or y\n", + "```\n", + "\n", + "- [ ] 0\n", + "- [ ] False\n", + "- [ ] True\n", + "- [ ] 12" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 4. Что выведет?\n", + "\n", + "```python\n", + "print(r\"//\\\\\")\n", + "```\n", + "\n", + "- [ ] `//\\\\`\n", + "- [ ] `//\\`\n", + "- [ ] `/\\`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 5. Выберите правильные варианты объявления строки в Python?\n", + "\n", + "- [ ] \"строка\"\n", + "- [ ] f\"строка\"\n", + "- [ ] r\"строка\"\n", + "- [ ] 'строка'\n", + "- [ ] \"\"\"строка\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 6. Отметьте правдивые факты про строки\n", + "\n", + "- [ ] строки в Python неизменяемые\n", + "- [ ] строки - это последовательность юникодных символов\n", + "- [ ] строки в Python - это последовательность чисел от 0 до 255" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 7. Что получится в результате выполнения?\n", + "\n", + "```python\n", + "\"a\" * 3\n", + "```\n", + "\n", + "- [ ] Упадет исключение\n", + "- [ ] Строка \"aaa\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 8. Что получится в результате выполнения среза?\n", + "\n", + "```python\n", + "\"привет\"[::-1]\n", + "```\n", + "\n", + "- [ ] \"т\"\n", + "- [ ] \"тевирп\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 9. Срез строки\n", + "\n", + "```python\n", + "\"привет\"[:]\n", + "```\n", + "\n", + "- [ ] вернет пустую строку\n", + "- [ ] приведет к исключению SyntaxError\n", + "- [ ] вернет копию строки \"привет\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 10. `bool(\"\")`\n", + "\n", + "- [ ] False\n", + "- [ ] True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 11. Истинные выражения про байтовые строки\n", + "\n", + "- [ ] Это последовательность чисел от 0 до 255\n", + "- [ ] Это последовательность юникодных символов" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 12. Какой метод превратит строку в байтовую строку?\n", + "\n", + "- [ ] encode()\n", + "- [ ] decode()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 13. Какие из записей не приведут к исключению?\n", + "\n", + "- [ ] b\"test\"\n", + "- [ ] b\"тест\"\n", + "- [ ] b\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 14. Что выведет программа?\n", + "\n", + "```python\n", + "x = \"Москва\"\n", + "if \"ква\" not in x:\n", + " print(\"1\")\n", + "elif \"ва\" not in x:\n", + " print(\"2\")\n", + "else:\n", + " print(\"3\")\n", + "```\n", + "\n", + "- [ ] 3\n", + "- [ ] 1\n", + "- [ ] 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 15. Что выведет программа?\n", + "\n", + "```python\n", + "n = 0\n", + "while n < 3:\n", + " if n > 0:\n", + " continue\n", + " else:\n", + " break\n", + " n += 1\n", + "\n", + "print(n)\n", + "```\n", + "\n", + "- [ ] 2\n", + "- [ ] 0\n", + "- [ ] Это бесконечный цикл\n", + "- [ ] 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 16. Что выведет программа?\n", + "\n", + "```python\n", + "s = \"\"\n", + "\n", + "for i in range(2, 10, 2):\n", + " s += str(i)\n", + "\n", + "print(s)\n", + "```\n", + "\n", + "- [ ] 2468\n", + "- [ ] 246810\n", + "- [ ] 23456789" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/3. Базовые типы и конструкции/Тест на типы и конструкции.ipynb b/1. Введение в Python/3. Базовые типы и конструкции/Тест на типы и конструкции.ipynb new file mode 100644 index 0000000..37c0703 --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/Тест на типы и конструкции.ipynb @@ -0,0 +1,262 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Тест на типы и конструкции #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. 10 / 2 – что получится в результате деления?\n", + "\n", + "- [x] Вещественное число 5.0\n", + "- [ ] Целое число 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. True или False?\n", + "\n", + "```python\n", + "bool(0.0)\n", + "```\n", + "\n", + "- [ ] True\n", + "- [x] False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3. Чему равно name?\n", + "\n", + "```python\n", + "x = 0\n", + "y = 12\n", + "name = x or y\n", + "```\n", + "\n", + "- [ ] 0\n", + "- [ ] False\n", + "- [ ] True\n", + "- [x] 12" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "4. Что выведет?\n", + "\n", + "```python\n", + "print(r\"//\\\\\")\n", + "```\n", + "\n", + "- [x] `//\\\\`\n", + "- [ ] `//\\`\n", + "- [ ] `/\\`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "5. Выберите правильные варианты объявления строки в Python?\n", + "\n", + "- [x] \"строка\"\n", + "- [x] f\"строка\"\n", + "- [x] r\"строка\"\n", + "- [x] 'строка'\n", + "- [x] \"\"\"строка\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "6. Отметьте правдивые факты про строки\n", + "\n", + "- [x] строки в Python неизменяемые\n", + "- [x] строки - это последовательность юникодных символов\n", + "- [ ] строки в Python - это последовательность чисел от 0 до 255" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "7. Что получится в результате выполнения?\n", + "\n", + "```python\n", + "\"a\" * 3\n", + "```\n", + "\n", + "- [ ] Упадет исключение\n", + "- [x] Строка \"aaa\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "8. Что получится в результате выполнения среза?\n", + "\n", + "```python\n", + "\"привет\"[::-1]\n", + "```\n", + "\n", + "- [ ] \"т\"\n", + "- [x] \"тевирп\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "9. Срез строки\n", + "\n", + "```python\n", + "\"привет\"[:]\n", + "```\n", + "\n", + "- [ ] вернет пустую строку\n", + "- [ ] приведет к исключению SyntaxError\n", + "- [x] вернет копию строки \"привет\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "10. `bool(\"\")`\n", + "\n", + "- [x] False\n", + "- [ ] True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "11. Истинные выражения про байтовые строки\n", + "\n", + "- [x] Это последовательность чисел от 0 до 255\n", + "- [ ] Это последовательность юникодных символов" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "12. Какой метод превратит строку в байтовую строку?\n", + "\n", + "- [x] encode()\n", + "- [ ] decode()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "13. Какие из записей не приведут к исключению?\n", + "\n", + "- [x] b\"test\"\n", + "- [ ] b\"тест\"\n", + "- [x] b\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "14. Что выведет программа?\n", + "\n", + "```python\n", + "x = \"Москва\"\n", + "if \"ква\" not in x:\n", + " print(\"1\")\n", + "elif \"ва\" not in x:\n", + " print(\"2\")\n", + "else:\n", + " print(\"3\")\n", + "```\n", + "\n", + "- [x] 3\n", + "- [ ] 1\n", + "- [ ] 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "15. Что выведет программа?\n", + "\n", + "```python\n", + "n = 0\n", + "while n < 3:\n", + " if n > 0:\n", + " continue\n", + " else:\n", + " break\n", + " n += 1\n", + "\n", + "print(n)\n", + "```\n", + "\n", + "- [ ] 2\n", + "- [x] 0\n", + "- [ ] Это бесконечный цикл\n", + "- [ ] 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "16. Что выведет программа?\n", + "\n", + "```python\n", + "s = \"\"\n", + "\n", + "for i in range(2, 10, 2):\n", + " s += str(i)\n", + "\n", + "print(s)\n", + "```\n", + "\n", + "- [x] 2468\n", + "- [ ] 246810\n", + "- [ ] 23456789" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/3. Базовые типы и конструкции/Тест на типы и конструкции.pdf b/1. Введение в Python/3. Базовые типы и конструкции/Тест на типы и конструкции.pdf new file mode 100644 index 0000000..e4867b4 Binary files /dev/null and b/1. Введение в Python/3. Базовые типы и конструкции/Тест на типы и конструкции.pdf differ diff --git a/1. Введение в Python/3. Базовые типы и конструкции/Численные типы.ipynb b/1. Введение в Python/3. Базовые типы и конструкции/Численные типы.ipynb new file mode 100644 index 0000000..a8ead5e --- /dev/null +++ b/1. Введение в Python/3. Базовые типы и конструкции/Численные типы.ipynb @@ -0,0 +1,821 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Базовые типы: численные типы #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На этой лекции мы начинаем знакомство с основными базовыми типами, которые есть в Python.\n", + "\n", + "## Целые числа (int) ##\n", + "\n", + "Первым в нашем списке будет идти тип int, это целые числа. Чтобы объявить целое число достаточно писать `num = 13`, тем самым мы связываем имя переменной `num` с малочисленным объектом со значением 13, точно также можно присвоить переменной 0 либо отрицательно целое число." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "num = 13\n", + "num = 0\n", + "num = -1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Начиная с Python 3.6 появилась возможность разделять порядки символом нижнего подчеркивания в длинных числах, тем самым мы можем улучшить читаемость этих чисел в исходном коде." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "kpop = 10_000_000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Раз уж мы заговорили о длинных числах, стоит отметить, что Python поддерживает длинную арифметику при работе с целыми числами, то есть поддерживаются числа неограниченной длины. Прежде чем продолжить, давайте разберем еще одну встроенную функцию, которая есть в языке, это функция `Type`. Функция `type` доступна в глобальном пространстве имен и она позволяет в рантайме, в процессе работы программы посмотреть на тип объекта, если мы объявим переменную `num` и свяжем ее с малочисленным объектом 13, а затем посмотрим какой у неё тип, то мы увидим, что это класс `int`, то это действительно тип `integer` и в дальнейшем мы видим, что встроенные типы Python они реализованы как классы, об этом я буду говорить в третьем блоке нашего курса. Ну а сейчас просто важно запомнить, что функция `Type` позволяет увидеть тип объекта." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "int" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(kpop)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Вещественные числа (float) ##\n", + "\n", + "Также Python умеет работать с вещественными числами, то есть с числами с плавающей точкой, это тип `float`. Чтобы объявить вещественное число, можно написать `pi = 13.4`, то есть точка используется для того чтобы разделить целую и дробную часть вещественного числа, то же самое для 0, и то же самое для отрицательного вещественного числа. По аналогии с типом `int` мы можем использовать символ нижнего подчёркивания, для того, чтобы разделять порядки в длинном вещественном числе. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "pi = 13.4\n", + "e = 2.718_281_828_459_045" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также Python поддерживает экспоненциальную нотацию записи вещественного числа, а, как мы можем видеть `1.5Е2`, на самом деле это полтора, умножить на 10 в степени 2, то есть 150." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "150.0" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1.5E2" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "150.0" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1.5 * 10 ** 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Конвертация типов ##\n", + "\n", + "Теперь поговорим о ещё одном важном аспекте - о конвертации типов. Посмотрим на пример, предположим, у нас есть переменная `num`, которая связана с вещественным объектом со значением 150,2, и мы видим что это тип `float`. В любой момент мы можем воспользоваться встроенным классом `int`, чтобы переменную, которая связана с типом `float`, перевести в объект типа `integer` и затем мы можем преобразовать `integer` обратно к вещественному типу, используя класс `float`, тем самым в процессе работы программы мы можем из одного типа конвертировать переменные в другой тип." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "150.2" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num = 150.2\n", + "num" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "150" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num = int(num)\n", + "num" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "150.0" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "float(num)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Обратите внимание, что в процессе наших конвертаций на примере, мы потеряли дробную часть вещественного числа.\n", + "\n", + "## Комплексные числа (complex) ##\n", + "\n", + "Также Python умеет работать с комплексными числами, это тип комплекс, для того, чтобы определить комплексное число нужно для мнимой части использовать символ `J`, посмотрите на пример, мы определили комплексное число `14 + 1J` и можем убедиться, что его тип действительно комплекс, используя встроенную функцию `Type`." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(14+1j)" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num = 14 + 1J\n", + "num" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "complex" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(num)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "А также, чтобы достучаться до реальной и мнимой части комплексного числа, мы можем воспользоваться атрибутами `real` и `imag`." + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "14.0" + ] + }, + "execution_count": 109, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num = 14 + 1J\n", + "num.real" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num.imag" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Что стоит отметить? Что в Pyton-e есть еще два модуля, которые позволяют работать с числами, эти модули очень полезны, мы не будем рассматривать их подробно, но знать о них нужно. В первую очередь, это модуль `deccimal`, который позволяет работать с вещественными числами с фиксированной точностью, а второй модуль это модуль `fractions`, который позволяет работать с рациональными числами, проще говоря с дробями.\n", + "\n", + "## Основные операции с числами ##\n", + "\n", + "Теперь посмотрим на основные операции с числами, как я уже говорил, Python хорош в качестве калькулятора и, конечно же, он умеет работать с числами очень хорошо. Для того, чтобы сложить два числа, мы используем просто математический оператор плюс. 1+1=2, сумма двух целых чисел дает нам целое число, если мы складываем целое число и вещественное число, то результат у нас получается вещественный. То же самое с вычитанием, если мы вычитаем из целого целое, получается целое число, если мы вычитаем из вещественного целое, то результат вещественный. Для того, чтобы разделить числа, мы можем воспользоваться символом прямого слэша, и обратите внимание, что результат деления всегда вещественный. Делить на 0 нельзя. Если мы попытаемся разделить на 0, то мы получим исключение `ZeroDivisionError`. Про исключения, как я уже говорил, мы с вами будем говорить в дальнейшем, научим вас их отлавливать и обрабатывать. Для того чтобы умножить числа, мы можем использовать звездочку, а для того, чтобы возвести число в степень, мы можем использовать две звездочки, на примере мы возводим число 2 в 4 степень. Чтобы произвести целочисленное деление, мы используем два прямых слэша. Чтобы получить остаток от деления, используем процент. Важно подчеркнуть, что Python использует порядок операций в выражениях с числами такой, какой используется в правилах математики и,в если мы посмотрим на пример, то выражение 3 плюс 10 умножить на 3, сначала выполнится умножение, а потом сложение. Если же нам нужно подчеркнуть, что нужно сначала выполнить сложение, а потом результат сложения умножить на 3, то мы можем воспользоваться круглыми скобочками для этого." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Сложение:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 + 1" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.5" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 + 1.5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Вычитание:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "6 - 2" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4.0" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "6 - 2.0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Деление:" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3.0" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "6 / 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Делить на 0 нельзя:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "ename": "ZeroDivisionError", + "evalue": "division by zero", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;36m6\u001b[0m \u001b[1;33m/\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mZeroDivisionError\u001b[0m: division by zero" + ] + } + ], + "source": [ + "6 / 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Умножение:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "2 * 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Возведение числа в степень:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "16" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "2 ** 4" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Целочисленное деление:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "10 // 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Остаток от деления:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "10 % 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Порядок операций в выражениях с числами:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "33" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "3 + 10 * 3" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "39" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(3 + 10) * 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Побитовые операции ##\n", + "\n", + "Также Python поддерживает побитовые операции для чисел, среди них побитово или, побитово исключающие или, и так далее." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Побитовое или: 7\n", + "Побитовое исключающее или: 7\n", + "Побитовое и: 0\n", + "Битовый сдвиг влево: 32\n", + "Битовый сдвиг вправо: 2\n", + "Инверсия битов: -5\n" + ] + } + ], + "source": [ + "x = 4\n", + "y = 3\n", + "\n", + "print(\"Побитовое или:\", x | y)\n", + "print(\"Побитовое исключающее или:\", x ^ y)\n", + "print(\"Побитовое и:\", x & y)\n", + "print(\"Битовый сдвиг влево:\", x << 3)\n", + "print(\"Битовый сдвиг вправо:\", x >> 1)\n", + "print(\"Инверсия битов:\", ~x)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Задачка ##\n", + "\n", + "Теперь посмотрим на задачку. Задача найти расстояние между двумя точками в декартовых координатах, эта задача известна всем нам из школьного курса математики, и для того, чтобы ее решить по большому счёту, нам нужно найти гипотенузу прямоугольного треугольника. Давайте посмотрим на решение на Python.\n", + "\n", + "![График](graph.svg \"График\")" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.0\n" + ] + } + ], + "source": [ + "x1, y1 = 0, 0\n", + "x2 = 3\n", + "y2 = 4\n", + "\n", + "distance = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** .5\n", + "print(distance)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Вначале мы объявляем переменные, это координаты точек. Обратите внимание на самую верхнюю запись `x1, y1 = 0, 0`. Это синтаксис, который поддерживается Python'ом, он позволяет через запятую перечислить названия переменных, а далее, через запятую перечислить те значения, которые этим переменным нужно присвоить. Это очень удобно. Для того, чтобы найти расстояние, мы воспользуемся формулой, корень из суммы квадратов катетов, и здесь мы видим, как мы вычисляем расстояние. Вместо того, чтобы брать корень, мы возводим выражение в скобках в степени 0,5, что эквивалентно взятию корня. И далее печатаем на экран ответ, мы видим, что ответ получился правильный." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "А что ещё хочется отметить про синтаксис Python, это мы видели в предыдущем примере то, что можно на одной строчке присвоить значения нескольким переменным. Однако, тот же самый трюк позволяет, например, поменять значения у 2 переменных без использования временной переменной, то есть мы определяем две переменные, дальше пишем `a, b = b, a`, и значение переменных меняется местами. Также очень удобно." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 1\n" + ] + } + ], + "source": [ + "a = 1\n", + "b = 2\n", + "a, b = b, a\n", + "\n", + "print(a, b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Что еще можно сделать было бы? Мы могли бы записать вместо `x, y = 0, 0`, `x = y = 0`, тем самым мы бы связали переменную `x` и переменную `y` с объектом, с зачисленным объектом 0. Однако нужно быть внимательным. первый объект это изменяемый объект, а второй - это неизменяемые, изменяемые объекты это объекты, которые после создания могут менять свое значение, а неизменяемые, соответственно, это объекты, которые после создания свое значение не меняют. Примитивные основные типы, которые мы разбираем на этой неделе, по большому счету, не изменяемы, поэтому для нашей задачи запись `x = y = 0` была бы оправдана. В данном случае, если бы мы поменяли только `х`, например, добавили бы к `х` впоследствии единичку, у нас `х` стал бы равен 1, а `у` остался бы нулем." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 0\n" + ] + } + ], + "source": [ + "a = b = 0\n", + "a += 1\n", + "\n", + "print(a, b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Но если бы мы написали `х = у = пустой список`, а пустой список - это объект-контейнер, который мы будем разбирать на дальнейших неделях, он позволяет хранить последовательность объектов в себе, а этот объект уже изменяемый, поэтому, когда мы присваиваем `х = у = пустой список`, а затем в список `х` добавляем два элемента, то, если мы выведем на экран `х` и `у`, то мы увидим, что изменились значения обеих переменных. С этим нужно быть внимательным, это нюанс, который необходимо понимать и необходимо понимать, что в Python-е есть неизменяемые объекты и изменяемые объекты." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2] [1, 2]\n" + ] + } + ], + "source": [ + "a = b = list()\n", + "\n", + "a.append(1)\n", + "a.append(2)\n", + "\n", + "print(a, b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На этой лекции мы с вами поговорили об основных численных типах, которые есть в языке, рассмотрели математические операции с ними, узнали о конвертации типов, а также затронули тему изменяемых и не изменяемых объектов в Python. В дальнейших лекциях, мы продолжим знакомиться с основными типами в языке." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/equation.py b/1. Введение в Python/4. Организация кода и окружение/equation.py new file mode 100644 index 0000000..86b6625 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/equation.py @@ -0,0 +1,13 @@ +import sys + +a = int(sys.argv[1]) +b = int(sys.argv[2]) +c = int(sys.argv[3]) + +d = b ** 2 - 4 * a * c + +if d > 0: + print((-b + (b ** 2 - 4 * a * c) ** .5) / (2 * a)) + print((-b - (b ** 2 - 4 * a * c) ** .5) / (2 * a)) +elif not d: + print(-b / (2 * a)) \ No newline at end of file diff --git a/1. Введение в Python/4. Организация кода и окружение/location.py b/1. Введение в Python/4. Организация кода и окружение/location.py new file mode 100644 index 0000000..5e13384 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/location.py @@ -0,0 +1,7 @@ +import requests + +def get_location_info(): + return requests.get("http://ip-api.com/json/").json() + +if __name__ == "__main__": + print(get_location_info()) \ No newline at end of file diff --git a/1. Введение в Python/4. Организация кода и окружение/mymodule.py b/1. Введение в Python/4. Организация кода и окружение/mymodule.py new file mode 100644 index 0000000..2be013c --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/mymodule.py @@ -0,0 +1,4 @@ +from mypackage.utils import * + +if __name__ == "__main__": + print(multiply(2, 3)) \ No newline at end of file diff --git a/1. Введение в Python/4. Организация кода и окружение/mypackage/__init__.py b/1. Введение в Python/4. Организация кода и окружение/mypackage/__init__.py new file mode 100644 index 0000000..1ea375e --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/mypackage/__init__.py @@ -0,0 +1 @@ +print("importing mypackage") \ No newline at end of file diff --git a/1. Введение в Python/4. Организация кода и окружение/mypackage/utils.py b/1. Введение в Python/4. Организация кода и окружение/mypackage/utils.py new file mode 100644 index 0000000..46134b0 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/mypackage/utils.py @@ -0,0 +1,2 @@ +def multiply(a, b): + return a * b \ No newline at end of file diff --git a/1. Введение в Python/4. Организация кода и окружение/solution.py b/1. Введение в Python/4. Организация кода и окружение/solution.py new file mode 100644 index 0000000..acb41a2 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/solution.py @@ -0,0 +1,10 @@ +import sys + +digit_string = sys.argv[1] + +summa = 0 + +for i in digit_string: + summa += int(i) + +print(summa) \ No newline at end of file diff --git a/1. Введение в Python/4. Организация кода и окружение/stairs.py b/1. Введение в Python/4. Организация кода и окружение/stairs.py new file mode 100644 index 0000000..415c166 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/stairs.py @@ -0,0 +1,6 @@ +import sys + +num_steps = int(sys.argv[1]) + +for i in range(num_steps): + print((num_steps - i + 1) * " " + "#" * (i + 1)) \ No newline at end of file diff --git a/1. Введение в Python/4. Организация кода и окружение/Байткод.ipynb b/1. Введение в Python/4. Организация кода и окружение/Байткод.ipynb new file mode 100644 index 0000000..7f9bde8 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Байткод.ipynb @@ -0,0 +1,219 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "08735fdb", + "metadata": {}, + "source": [ + "# Байткод #" + ] + }, + { + "cell_type": "markdown", + "id": "764f3e24", + "metadata": {}, + "source": [ + "А сейчас давайте поговорим про байт-код.\n", + "\n", + "Помните, когда мы рассматривали организацию кода на Python, мы импортировали функции из модулей и пакетов, и Python автоматически создавал файлики с расширением.pyc. Я сказал, что это файлики, в которых содержится байт-код, то есть скомпилированное представление вашего кода в форме, удобной Python для интерпретирования.\n", + "\n", + "Давайте сейчас подробнее посмотрим, что это значит. Для этого мы создадим модуль, назовем его `mymodule`.\n", + "\n", + "Мы с вами это уже умеем делать, и внутри определим функцию `multiply`, точно такую же, которую мы с вами уже писали. Она принимает два аргумента и возвращает их произведение.\n", + "\n", + "```python\n", + "def multiply(a, b):\n", + " return a * b\n", + "```\n", + "\n", + "Функция определена, теперь мы можем ее импортировать. Давайте воспользуемся конструкцией `from`. `from mymodule import multiply`. `multiply` — это функция." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "03f059a0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "importing mypackage\n" + ] + } + ], + "source": [ + "from mymodule import multiply" + ] + }, + { + "cell_type": "markdown", + "id": "0c3e8648", + "metadata": {}, + "source": [ + "Как я уже сказал, практически всё в Python — это объект, не исключение и функция. Если мы посмотрим с помощью функции `dir` методы, которые содержит функция, мы увидим, что их много, в том числе у функции есть атрибут `__code__` с двумя подчеркиваниями, это `code object`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1374e3bc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']\n" + ] + } + ], + "source": [ + "print(dir(multiply))" + ] + }, + { + "cell_type": "markdown", + "id": "43989f9d", + "metadata": {}, + "source": [ + "По сути, это объект, который оборачивает тот код, который вы пишете. У этого объекта есть метод `co_code`, который содержит байт-код. Это байт-код, который Python сгенерировал для этой функции." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a3373ab6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "b'|\\x00|\\x01\\x14\\x00S\\x00'\n" + ] + } + ], + "source": [ + "print(multiply.__code__.co_code)" + ] + }, + { + "cell_type": "markdown", + "id": "eb706b6d", + "metadata": {}, + "source": [ + "Что это значит? Это значит, что наша функция была преобразована в байт-код, который представляет собой набор инструкций. В данном случае это байтовая строка, которая содержит инструкцию одну за другой, которые подаются на вход интерпретатору Python.\n", + "\n", + "Интерпретатор Python, по большому счету, это большая конструкция `switch` кода на C, которая, в зависимости от входящей инструкции байт-кода, предпринимает то или иное действие." + ] + }, + { + "cell_type": "markdown", + "id": "33872151", + "metadata": {}, + "source": [ + "Как мы видим, байт-код нечеловекочитаемый Мы можем привести его в человекочитаемый вид, используя модуль `dis` стандартной библиотеки Python.\n", + "\n", + "Давайте это сделаем. Заимпортируем модуль `dis` и с помощью метода `dis` модуля `dis` дисассемблируем нашу функцию в человекочитаемый вид. Что мы видим?" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e2ccab2f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 0 LOAD_FAST 0 (0)\n", + " 2 LOAD_FAST 1 (1)\n", + " 4 BINARY_MULTIPLY\n", + " 6 RETURN_VALUE\n" + ] + } + ], + "source": [ + "from dis import dis\n", + "\n", + "dis(multiply.__code__.co_code)" + ] + }, + { + "cell_type": "markdown", + "id": "42c24b47", + "metadata": {}, + "source": [ + "Мы видим, что наша функция на самом деле преобразовывается в байт-код, который состоит из четырех инструкций. Каждая из этих инструкций делает какое-то действие. Давайте посмотрим на каждую инструкцию подробно.\n", + "\n", + "Первая — это `LOAD_FAST`, это инструкция, которая загружает содержимое переменной `a` в память и помещает его на стек. Стек — это структура данных, последним пришёл - первым вышел (LIFO), которая определена на уровне C и используется Python для того, чтобы манипулировать ходом выполнения программы.\n", + "\n", + "Как только переменная `a` помещена на стек, мы переходим ко второй инструкции, которая также `LOAD_FAST`, она делает то же самое со значением переменной `b`, помещает значение переменной `b` на стек. Далее.\n", + "\n", + "Следующая инструкция — это `BINARY_MULTIPLY`. `BINARY_MULTIPLY` говорит интерпретатору Python, что нужно взять два самых верхних значения на стеке и произвести их умножение. Заметьте, что мы могли передать в функцию значение любого типа. Могли передать целые числа, могли вещественные. Могли бы передать даже строки, например. Но в результате преобразования нашей функции в байт-код байт-код получился тем же самым. Типизация начинает по большому счету работать тогда, когда Python выполняет ту или иную инструкцию.\n", + "\n", + "В данном случае, когда выполняется инструкция `BINARY_MULTIPLY`, Python посмотрит на тип объектов, и, например, если это два числа, произведет умножение двух чисел. Результат умножения он поместит на вершину стека.\n", + "\n", + "И последняя инструкция — это `RETURN_VALUE`. `RETURN_VALUE` берет вершину стека, это результат умножения наших значений `a` и `b`, и возвращает его. По большому счету, это то, как работает Python, как устроен байт-код, и любой код на Python, который вы будете писать, будет превращаться, компилироваться в байт-код. Этот байт-код будет выглядеть посложнее, чем мы с вами видели, потому что функции, которые вы будете писать, могут быть сложнее, содержать всевозможные циклы, условия и так далее. И будут использоваться соответствующие инструкции.\n", + "\n", + "Все эти инструкции задокументированы, их можно посмотреть как в исходном коде Python, так и в документации. Ну, и можно посмотреть на них сейчас, есть модуль в стандартной библиотеке `opcode`, который мы можем заимпортировать и напечатать, например, на экран `opmap`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b4d07e58", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'POP_TOP': 1, 'ROT_TWO': 2, 'ROT_THREE': 3, 'DUP_TOP': 4, 'DUP_TOP_TWO': 5, 'ROT_FOUR': 6, 'NOP': 9, 'UNARY_POSITIVE': 10, 'UNARY_NEGATIVE': 11, 'UNARY_NOT': 12, 'UNARY_INVERT': 15, 'BINARY_MATRIX_MULTIPLY': 16, 'INPLACE_MATRIX_MULTIPLY': 17, 'BINARY_POWER': 19, 'BINARY_MULTIPLY': 20, 'BINARY_MODULO': 22, 'BINARY_ADD': 23, 'BINARY_SUBTRACT': 24, 'BINARY_SUBSCR': 25, 'BINARY_FLOOR_DIVIDE': 26, 'BINARY_TRUE_DIVIDE': 27, 'INPLACE_FLOOR_DIVIDE': 28, 'INPLACE_TRUE_DIVIDE': 29, 'GET_AITER': 50, 'GET_ANEXT': 51, 'BEFORE_ASYNC_WITH': 52, 'BEGIN_FINALLY': 53, 'END_ASYNC_FOR': 54, 'INPLACE_ADD': 55, 'INPLACE_SUBTRACT': 56, 'INPLACE_MULTIPLY': 57, 'INPLACE_MODULO': 59, 'STORE_SUBSCR': 60, 'DELETE_SUBSCR': 61, 'BINARY_LSHIFT': 62, 'BINARY_RSHIFT': 63, 'BINARY_AND': 64, 'BINARY_XOR': 65, 'BINARY_OR': 66, 'INPLACE_POWER': 67, 'GET_ITER': 68, 'GET_YIELD_FROM_ITER': 69, 'PRINT_EXPR': 70, 'LOAD_BUILD_CLASS': 71, 'YIELD_FROM': 72, 'GET_AWAITABLE': 73, 'INPLACE_LSHIFT': 75, 'INPLACE_RSHIFT': 76, 'INPLACE_AND': 77, 'INPLACE_XOR': 78, 'INPLACE_OR': 79, 'WITH_CLEANUP_START': 81, 'WITH_CLEANUP_FINISH': 82, 'RETURN_VALUE': 83, 'IMPORT_STAR': 84, 'SETUP_ANNOTATIONS': 85, 'YIELD_VALUE': 86, 'POP_BLOCK': 87, 'END_FINALLY': 88, 'POP_EXCEPT': 89, 'STORE_NAME': 90, 'DELETE_NAME': 91, 'UNPACK_SEQUENCE': 92, 'FOR_ITER': 93, 'UNPACK_EX': 94, 'STORE_ATTR': 95, 'DELETE_ATTR': 96, 'STORE_GLOBAL': 97, 'DELETE_GLOBAL': 98, 'LOAD_CONST': 100, 'LOAD_NAME': 101, 'BUILD_TUPLE': 102, 'BUILD_LIST': 103, 'BUILD_SET': 104, 'BUILD_MAP': 105, 'LOAD_ATTR': 106, 'COMPARE_OP': 107, 'IMPORT_NAME': 108, 'IMPORT_FROM': 109, 'JUMP_FORWARD': 110, 'JUMP_IF_FALSE_OR_POP': 111, 'JUMP_IF_TRUE_OR_POP': 112, 'JUMP_ABSOLUTE': 113, 'POP_JUMP_IF_FALSE': 114, 'POP_JUMP_IF_TRUE': 115, 'LOAD_GLOBAL': 116, 'SETUP_FINALLY': 122, 'LOAD_FAST': 124, 'STORE_FAST': 125, 'DELETE_FAST': 126, 'RAISE_VARARGS': 130, 'CALL_FUNCTION': 131, 'MAKE_FUNCTION': 132, 'BUILD_SLICE': 133, 'LOAD_CLOSURE': 135, 'LOAD_DEREF': 136, 'STORE_DEREF': 137, 'DELETE_DEREF': 138, 'CALL_FUNCTION_KW': 141, 'CALL_FUNCTION_EX': 142, 'SETUP_WITH': 143, 'LIST_APPEND': 145, 'SET_ADD': 146, 'MAP_ADD': 147, 'LOAD_CLASSDEREF': 148, 'EXTENDED_ARG': 144, 'BUILD_LIST_UNPACK': 149, 'BUILD_MAP_UNPACK': 150, 'BUILD_MAP_UNPACK_WITH_CALL': 151, 'BUILD_TUPLE_UNPACK': 152, 'BUILD_SET_UNPACK': 153, 'SETUP_ASYNC_WITH': 154, 'FORMAT_VALUE': 155, 'BUILD_CONST_KEY_MAP': 156, 'BUILD_STRING': 157, 'BUILD_TUPLE_UNPACK_WITH_CALL': 158, 'LOAD_METHOD': 160, 'CALL_METHOD': 161, 'CALL_FINALLY': 162, 'POP_FINALLY': 163}\n" + ] + } + ], + "source": [ + "from opcode import opmap\n", + "\n", + "print(opmap)" + ] + }, + { + "cell_type": "markdown", + "id": "a86677b4", + "metadata": {}, + "source": [ + "Мы видим, что на самом деле инструкций существует очень много. И, так или иначе, они в определенный момент будут задействованы. \n", + "\n", + "Что хотелось бы отметить? Знание о том, что ваш код компилируется в байт-код, может оказаться в определенный момент очень полезным. И, как минимум, вы не будете бояться того, что Python автоматически создает скомпилированные байт-код файлики в ваших директориях.\n", + "\n", + "Мы посмотрели на то, как Python преобразовывает код в байт-код, и теперь вы знаете, как это устроено." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Виртуальное окружение на Windows.ipynb b/1. Введение в Python/4. Организация кода и окружение/Виртуальное окружение на Windows.ipynb new file mode 100644 index 0000000..6808184 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Виртуальное окружение на Windows.ipynb @@ -0,0 +1,61 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "83ee0fe9", + "metadata": {}, + "source": [ + "# Виртуальное окружение на Windows #\n", + "\n", + "Работа с виртуальным окружением на **Windows** немного отличается от того, что мы видели в **Unix**-подобных систем. Чтобы создать виртуальное окружение на **Windows** запустите в терминале (мы рассказывали как открыть терминал в документе про установку **Python 3**):\n", + "\n", + "```\n", + "python3 -m venv c:\\path\\to\\myenv\n", + "```\n", + "\n", + "Где вместо `c:\\path\\to\\myenv` укажите путь до папки с виртуальным окружением, которую вы хотите создать.\n", + "\n", + "После того как скрипт отработает, вы можете активировать виртуальное окружение с помощью:\n", + "\n", + "```\n", + "c:\\path\\to\\myenv\\Scripts\\activate.bat\n", + "```\n", + "\n", + "Чтобы деактивировать виртуальное окружение:\n", + "\n", + "```\n", + "c:\\path\\to\\myenv\\Scripts\\deactivate.bat\n", + "```\n", + "\n", + "После активации виртуального окружения вы можете устанавливать в него дополнительные сторонние библиотеки точно так же, как мы показывали в видео, например:\n", + "\n", + "```\n", + "pip install requests\n", + "```\n", + "\n", + "Обратите внимание, что в то время как на **Unix** системах интерпретатор **python** находится в директории `bin/` внутри виртуального окружения – на **Windows** интерпретатор будет находиться внутри директории `Scripts/` созданного виртуального окружения." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Виртуальное окружение.ipynb b/1. Введение в Python/4. Организация кода и окружение/Виртуальное окружение.ipynb new file mode 100644 index 0000000..6feec15 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Виртуальное окружение.ipynb @@ -0,0 +1,334 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9ccc4a00", + "metadata": {}, + "source": [ + "# Виртуальное окружение #" + ] + }, + { + "cell_type": "markdown", + "id": "cdd20ab8", + "metadata": {}, + "source": [ + "На этой лекции мы с вами посмотрим, как работать со сторонними библиотеками с ресурсов `pypi.org`, мы установим нашу систему клиент-сервер серверное приложение `Jupiter` ноутбук, которые я активно применяю для создания слайдов в наших уроках. Однако это не единственное применение `Jupiter` Ноутбук, это очень распространённое приложение, которое применяется повсеместно разработчиками на Python, а также учеными для презентации своей работы, для того чтобы показывать сниппеты кода на Python, алгоритмы, результаты проведенной работы и показывать это интерактивно." + ] + }, + { + "cell_type": "markdown", + "id": "50ad6169", + "metadata": {}, + "source": [ + "Для того чтобы начать мы должны поговорить о том как поставить сторонний пакет на нашу операционную систему, для этого в Python существует замечательная утилита, которая называется `pip`. наберём `pip install` и название сторонних библиотек, которые мы хотим поставить, в данном случае написал `requests`, это замечательная библиотека для работы с `http` запросами. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4d11a3c7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: requests in c:\\programdata\\anaconda3\\lib\\site-packages (2.25.1)\n", + "Requirement already satisfied: idna<3,>=2.5 in c:\\programdata\\anaconda3\\lib\\site-packages (from requests) (2.10)\n", + "Requirement already satisfied: chardet<5,>=3.0.2 in c:\\programdata\\anaconda3\\lib\\site-packages (from requests) (4.0.0)\n", + "Requirement already satisfied: certifi>=2017.4.17 in c:\\programdata\\anaconda3\\lib\\site-packages (from requests) (2020.12.5)\n", + "Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:\\programdata\\anaconda3\\lib\\site-packages (from requests) (1.26.4)\n" + ] + } + ], + "source": [ + "! pip install requests" + ] + }, + { + "cell_type": "markdown", + "id": "5c66f0b0", + "metadata": {}, + "source": [ + "`pip install request` ставит request в нашу систему, мы сейчас видим что библиотека `requests` уже установлена в глобальный директорию Python, это не очень удобно, мы не хотим трогать эту установленную версию библиотеки, поэтому следующим шагом нам нужно будет создать виртуальное окружение." + ] + }, + { + "cell_type": "markdown", + "id": "fc9717e7", + "metadata": {}, + "source": [ + "Виртуальное окружение в Python это окружение, которое позволяет изолировать зависимости для определенного проекта. Например, предположим вы разрабатываете на машине два проекта, причем в этих проектах используется одна и та же библиотека, но разных версий. В одном проекте чуть более старая версия этой библиотеки, в другом чуть более новая. Виртуальное окружение позволяет вам изолировать эти две зависимости друг от друга, так что вы можете работать над двумя проектами параллельно на одном компьютере. Это очень удобно. Тем самым, обновляя библиотеку в одном проекте, вы не сломаете код другого проекта." + ] + }, + { + "cell_type": "markdown", + "id": "57a1610b", + "metadata": {}, + "source": [ + "Чтобы создать виртуальное окружение мы воспользуемся модулем, который идет в поставке Рython3 из коробки он называется `venv`." + ] + }, + { + "cell_type": "markdown", + "id": "393b562d", + "metadata": {}, + "source": [ + "Чтобы создать виртуальное окружение мы пишем `python3 -m venv` и дальше название директории, в которой будет создано наше виртуальное окружение." + ] + }, + { + "cell_type": "markdown", + "id": "cde9b0a1", + "metadata": {}, + "source": [ + "Однако перед этим давайте опять создадим директорию, в которой мы будем работать и перейдём в нее. А вот теперь мы можем применить ту команду, о которой я говорил `python3 -m venv .env` запустить ее." + ] + }, + { + "cell_type": "markdown", + "id": "6e3d4421", + "metadata": {}, + "source": [ + "В этот момент создается виртуальное окружение и если мы посмотрим на структуру директорий, мы видим что создалось папочка `env`, да, как мы ее и назвали. Давайте посмотрим что внутри этой папочки, ограничимся уровнем вложенности три и мы видим что внутри папочки `env` создалось несколько директорий, одна из них `bin`, другая `include`, а также `lib`.\n", + "\n", + "```shell\n", + "tree -L 3\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "52a2b541", + "metadata": {}, + "outputs": [], + "source": [ + "! python -m venv .env" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dc02a475", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "‘вагЄвга  Ї Ї®Є\n", + "‘ҐаЁ©­л© ­®¬Ґа ⮬ : 044F0438 581B:1E7B\n", + "C:\\USERS\\MIKHAYLOVAF\\PYTHON\\1. ‚‚…„…Ќ€… ‚ PYTHON\\4. ЋђѓЂЌ€‡Ђ–€џ ЉЋ„Ђ € ЋЉђ“†…Ќ€…\\.ENV\n", + "ГДДДInclude\n", + "ГДДДLib\n", + "і АДДДsite-packages\n", + "і ГДДДpip\n", + "і і ГДДД_internal\n", + "і і і ГДДДcli\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДcommands\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДdistributions\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДindex\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДmodels\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДnetwork\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДoperations\n", + "і і і і ГДДДbuild\n", + "і і і і і АДДД__pycache__\n", + "і і і і ГДДДinstall\n", + "і і і і і АДДД__pycache__\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДreq\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДresolution\n", + "і і і і ГДДДlegacy\n", + "і і і і і АДДД__pycache__\n", + "і і і і ГДДДresolvelib\n", + "і і і і і АДДД__pycache__\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДutils\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДvcs\n", + "і і і і АДДД__pycache__\n", + "і і і АДДД__pycache__\n", + "і і ГДДД_vendor\n", + "і і і ГДДДcachecontrol\n", + "і і і і ГДДДcaches\n", + "і і і і і АДДД__pycache__\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДcertifi\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДchardet\n", + "і і і і ГДДДcli\n", + "і і і і і АДДД__pycache__\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДcolorama\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДdistlib\n", + "і і і і ГДДД_backport\n", + "і і і і і АДДД__pycache__\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДhtml5lib\n", + "і і і і ГДДДfilters\n", + "і і і і і АДДД__pycache__\n", + "і і і і ГДДДtreeadapters\n", + "і і і і і АДДД__pycache__\n", + "і і і і ГДДДtreebuilders\n", + "і і і і і АДДД__pycache__\n", + "і і і і ГДДДtreewalkers\n", + "і і і і і АДДД__pycache__\n", + "і і і і ГДДД_trie\n", + "і і і і і АДДД__pycache__\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДidna\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДmsgpack\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДpackaging\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДpep517\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДpkg_resources\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДprogress\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДrequests\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДresolvelib\n", + "і і і і ГДДДcompat\n", + "і і і і і АДДД__pycache__\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДtoml\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДurllib3\n", + "і і і і ГДДДcontrib\n", + "і і і і і ГДДД_securetransport\n", + "і і і і і і АДДД__pycache__\n", + "і і і і і АДДД__pycache__\n", + "і і і і ГДДДpackages\n", + "і і і і і ГДДДbackports\n", + "і і і і і і АДДД__pycache__\n", + "і і і і і ГДДДssl_match_hostname\n", + "і і і і і і АДДД__pycache__\n", + "і і і і і АДДД__pycache__\n", + "і і і і ГДДДutil\n", + "і і і і і АДДД__pycache__\n", + "і і і і АДДД__pycache__\n", + "і і і ГДДДwebencodings\n", + "і і і і АДДД__pycache__\n", + "і і і АДДД__pycache__\n", + "і і АДДД__pycache__\n", + "і ГДДДpip-20.2.3.dist-info\n", + "і ГДДДpkg_resources\n", + "і і ГДДДextern\n", + "і і і АДДД__pycache__\n", + "і і ГДДД_vendor\n", + "і і і ГДДДpackaging\n", + "і і і і АДДД__pycache__\n", + "і і і АДДД__pycache__\n", + "і і АДДД__pycache__\n", + "і ГДДДsetuptools\n", + "і і ГДДДcommand\n", + "і і і АДДД__pycache__\n", + "і і ГДДДextern\n", + "і і і АДДД__pycache__\n", + "і і ГДДД_distutils\n", + "і і і ГДДДcommand\n", + "і і і і АДДД__pycache__\n", + "і і і АДДД__pycache__\n", + "і і ГДДД_vendor\n", + "і і і ГДДДpackaging\n", + "і і і і АДДД__pycache__\n", + "і і і АДДД__pycache__\n", + "і і АДДД__pycache__\n", + "і ГДДДsetuptools-49.2.1.dist-info\n", + "і АДДД__pycache__\n", + "АДДДScripts\n" + ] + } + ], + "source": [ + "! tree .env" + ] + }, + { + "cell_type": "markdown", + "id": "f0562d32", + "metadata": {}, + "source": [ + "Директория `bin` содержит исполняемый файл python который на самом деле ссылается на наш глобально установленный python, также внутри `bin` директория Virtualenv есть утилита `pip`, а также другие вещи важные из которых скрипт `activate`, этот скрипт позволит нам активировать наше виртуальное окружение. Также обратите внимание на папочку `lib`, именно в эту директорию будут ставится сторонние зависимости когда мы будем работать в виртуальном окружении. Итак, давайте активируем виртуальное окружение. Обратите внимание, что все примеры, которые я здесь показываю для работы с виртуальным окружением в данном случае я работаю на Linux, а вот для Windows виртуальное окружение, структура его директории будут немного отличаться. Все остальные концепции, о которых я буду рассказывать справедливы для всех систем.\n", + "\n", + "- [Виртуальное окружение на Windows](Виртуальное%20окружение%20на%20Windows.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "643ab7b8", + "metadata": {}, + "source": [ + "Итак мы должны активировать виртуальное окружение, это делается с помощью команды `source` на unix-подобных системах и мы должны вызвать `source env/bin/activate`, чтобы деактивировать виртуальное окружение, мы набираем `deactivite`." + ] + }, + { + "cell_type": "markdown", + "id": "13a3e80c", + "metadata": {}, + "source": [ + "Однако нам всё же нужно работать в виртуальном окружении поэтому давайте его активируем опять и теперь установим в него сторонний модуль. Установим библиотечку `requests`, с которой мы уже начинали, она быстро устанавливается и теперь, когда мы пишем `python`, мы уже можем писать не `python3`, а `python` потому что, в данном случае мы находится в виртуальном окружении и `python` ссылается на `python3`.\n", + "\n", + "```shell\n", + "pip install requests\n", + "```\n", + "\n", + "```python\n", + "import requests\n", + "```\n", + "\n", + "Мы можем делать импорт только что установленной библиотечки `request`.\n", + "\n", + "- [Работа offline](Работа%20offline.ipynb)\n", + "\n", + "Продолжим идти к нашей цели, напомню, это `Jupiter` ноутбук и установим его, делается это точно также `pip install jupiter` ноутбук содержится в пакете `jupiter`, запускаем. Как вы видите пошёл процесс установки, в этот момент `pip` утилита загружает все необходимые зависимости пакета `jupither` с ресурса по `pip.org` а также зависимости зависимостей, среди которых на самом деле достаточно много всевозможных пакетов, но тем не менее, всё это происходит автоматически и когда процесс заканчивается у вас всё должно быть успешно установлено.\n", + "\n", + "Вот мы видим на экране, что у нас всё получилось и прежде чем запустить `Jupiter` ноутбук хотелось бы обратить внимание на ещё на один полезный пакет, который установился вместе с зависимостями `Jupiter` это пакет `ipython`. `ipython` это расширенная версия интерактивный интерпретатор Python. Если вы любите экспериментировать в интерактивном интерпретаторе Python, то рассмотрите `ipython` в качестве альтернативы, помимо подсветки синтаксиса, которая присутствует в `ipython`, там есть ещё много классных особенностей, которые позволят вам упростить жизнь. Например, это автодополнение, хранение истории и всевозможные полезные макросы." + ] + }, + { + "cell_type": "markdown", + "id": "a59124a3", + "metadata": {}, + "source": [ + "Теперь мы можем попробовать всё-таки запустить наш `Jupiter` ноутбук, он установлен в виртуальное окружение, теперь его исполняемые файлы находятся в папочке `bin` виртуального окружения и командой `jupiter-notebook` мы можем его запустить. Как я говорил это клиент-серверное веб-приложение, которое запускается в веб-браузере, веб-браузер у меня открылся автоматически и мы внутри этого веб-приложения можем создавать наши ноутбуки. Нажимаю на кнопочку `New`, выбираем `Python3` и у нас открывается наш ноутбук, по сути он состоит из ячеек в которых мы можем набирать питоновский код, делать любые импорты пакетов, которые у нас установлены и по сути всё, что мы можем делать в интерактивном интерпретаторе Python, мы можем сделать здесь, однако все эти ноутбуки можно сохранить, можно вернуться к ним в дальнейшем и проглядеть заново, перезапустить ячейки, это очень удобно. Как вы можете видеть я слайдах использую Jupiter ноутбук в качестве инструмента для создания слайдов например.\n", + "\n", + "На этой лекции мы с вами познакомились с виртуальным окружением в Python, научились ставить пакеты в нашу операционную систему, также мы установили Jupiter ноутбук, который является полезным инструментом для всех программистов на Python." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Корни квадратного уравнения.ipynb b/1. Введение в Python/4. Организация кода и окружение/Корни квадратного уравнения.ipynb new file mode 100644 index 0000000..18f8693 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Корни квадратного уравнения.ipynb @@ -0,0 +1,109 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4cd45516", + "metadata": {}, + "source": [ + "# Корни квадратного уравнения #" + ] + }, + { + "cell_type": "markdown", + "id": "2aa19998", + "metadata": {}, + "source": [ + "Python очень активно применяют ученые со всего мира в своих исследованиях. В этом задании мы с вами попробуем с помощью Python найти корни квадратного уравнения.\n", + "\n", + "Напомним, что квадратное уравнение выглядит следующим образом:\n", + "\n", + "$$ ax^2 + bx + c = 0 $$\n", + "\n", + "На вход программе мы подадим коэффициенты $a$, $b$ и $c$ - ваша цель напечатать корни квадратного уравнения (каждый с новой строки).\n", + "\n", + "Помним формулу дискриминанта и корней:\n", + "\n", + "$$ D = b^2 - 4ac $$\n", + "$$ x_{1,2} = \\frac {-b \\pm \\sqrt {b^2 - 4ac} } {2a} $$\n", + "\n", + "Коэффициенты a, b, c вы можете получить следующим образом:\n", + "\n", + "```\n", + "import sys\n", + "\n", + "a = int(sys.argv[1])\n", + "b = int(sys.argv[2])\n", + "c = int(sys.argv[3])\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "af54e415", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4.0\n", + "-1.0\n" + ] + } + ], + "source": [ + "! python equation.py 1 -3 -4" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "dfaa7006", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-0.3333333333333333\n" + ] + } + ], + "source": [ + "! python equation.py 9 6 1" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "93119b50", + "metadata": {}, + "outputs": [], + "source": [ + "! python equation.py 2 -3 4" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Модули и пакеты.ipynb b/1. Введение в Python/4. Организация кода и окружение/Модули и пакеты.ipynb new file mode 100644 index 0000000..8faf6dc --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Модули и пакеты.ipynb @@ -0,0 +1,759 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Модули и пакеты #" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте поговорим об организации кода на Python.\n", + "\n", + "В реальных проектах, конечно, никто код в один файлик не пишет. Это очень быстро становится громоздким и неподдерживаемым. Представьте проекты, где тысячи или даже сотни тысяч строк кода. Конечно, код надо разделять по смыслу, по назначению. И в Python'е это разделение предоставляется механизмом модулей и пакетов.\n", + "\n", + "Что такое модуль в Python? Модуль в Python мы с вами уже создавали, когда экспериментировали с интерактивным интерпретатором. Модуль в Python - это, по сути, файлик с расширением.py, который содержит определения переменных, функций, классов, и который также может содержать импорты других модулей. Давайте мы с вами начнем знакомство с модулем и создадим наш новый модуль." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Перейдем в консоль, создадим директорию `playground`, в которой мы будем работать. Перейдем в нее.\n", + "\n", + "```shell\n", + "mkdir playground\n", + "cd playground\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте создадим модуль, назовем его `mymodule`.\n", + "\n", + "```shell\n", + "vim mymodule.py\n", + "```\n", + "\n", + "Опять же обратите внимание на расширение.py." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Внутри `mymodule` мы можем писать код. Как я говорил, модуль может содержать импорты. Давайте начнем знакомиться с импортами и посмотрим на импорт модуля из стандартной библиотеки. Это делается с помощью ключевого слова `import`. Мы сейчас заимпортируем модуль стандартной библиотеки `sys`.\n", + "\n", + "```python\n", + "import sys\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Модуль `sys` содержит всевозможные переменные, структуры, функции для того, чтобы посмотреть на что-то, связанное с интерпретатором Python. В данном случае мы посмотрим на то, где Python ищет модули по умолчанию. Эта информация находится внутри переменной `sys.path`, давайте напечатаем ее на экран.\n", + "\n", + "```python\n", + "print(sys.path)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Как мы с вами уже делали, мы можем запустить модуль напрямую с помощью `python3`. Мы видим на экране, что с режима переменной `sys.path` вывелось. Это список директорий, в которых Python по умолчанию ищет модули. Обратите внимание, что на первом месте написана директория, в которой мы сейчас находимся.\n", + "\n", + "```shell\n", + "python3 mymodule.py\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['C:\\\\Users\\\\MikhaylovAF\\\\Python\\\\1. Введение в Python\\\\4. Организация кода и окружение', 'C:\\\\ProgramData\\\\Anaconda3\\\\python38.zip', 'C:\\\\ProgramData\\\\Anaconda3\\\\DLLs', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib', 'C:\\\\ProgramData\\\\Anaconda3', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\locket-0.2.1-py3.8.egg', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\win32', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\win32\\\\lib', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\Pythonwin']\n" + ] + } + ], + "source": [ + "! python mymodule.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте убедимся в этом. Утилита `pwd` показывает нашу текущую директорию. То есть Python будет искать модули в этой директории в первую очередь, поэтому ничто нам не мешает запустить интерактивный интерпретатор Python и попробовать заимпортировать наш модуль. Сделаем это." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['C:\\\\Users\\\\MikhaylovAF\\\\Python\\\\1. Введение в Python\\\\4. Организация кода и окружение', 'C:\\\\ProgramData\\\\Anaconda3\\\\python38.zip', 'C:\\\\ProgramData\\\\Anaconda3\\\\DLLs', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib', 'C:\\\\ProgramData\\\\Anaconda3', '', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\locket-0.2.1-py3.8.egg', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\win32', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\win32\\\\lib', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\Pythonwin', 'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\site-packages\\\\IPython\\\\extensions', 'C:\\\\Users\\\\MikhaylovAF\\\\.ipython']\n" + ] + } + ], + "source": [ + "import mymodule" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`import mymodule`, и видим, что у нас получилось. Что важно отметить, это то, что если мы попробуем сделать `import mymodule` еще раз, то ничего на экран не выведется. Это связано с тем, что Python импортирует модуль только один раз. Он импортирует имя модуля в локальное пространство имен, и в дальнейшем, если мы делаем точно такой же импорт, импорта не происходит, потому что Python видит, что мы уже ранее этот модуль импортировали, импорт закеширован." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Когда происходит импорт, что вообще происходит? Python импортирует модуль и выполняет все инструкции, которые в этом модуле на верхнем уровне определены. Собственно он наши инструкции, которые мы написали в файлике `mymodule.py` и выполнил.\n", + "\n", + "Давайте пойдем дальше. Иногда модулей недостаточно. Иногда код нужно объединять по смыслу в более крупные объединения, такие как пакеты." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Пакеты в Python, по сути, это директория, которая содержит один или больше модулей. Внутри пакетов могут содержаться другие пакеты. Давайте посмотрим на примере и создадим пакет. Пакет, по сути, является тем же самым модулем. Но давайте создадим пакет, назовем его `mypackage`. Это директория, и внутри директории `mypackage` давайте создадим пустой файлик `__init__.py`. Обратите внимание на название этого файла. Оно начинается с двух символов подчеркивания, заканчивается ими и с расширением `py`.\n", + "\n", + "```shell\n", + "mkdir mypackage\n", + "touch mypackage/__init__.py\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Это специальный файл, который должен содержать пакет для того, чтобы интерпретироваться Python'ом как пакет. На самом деле начиная с Python'a 3.3 есть так называемый `mainspace packages`, который не требует наличия `__init__.py` файлика в них. Но мы будем с вами рассматривать обычные `regular packages`, которые вы обычно будете видеть на практике." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Что это за файлик `__init__.py`? Это файл, который будет выполняться каждый раз, когда мы импортируем наш пакет. Давайте посмотрим на примере. Откроем наш модуль, который мы писали, `mymodule.py` и заменим импорты. Теперь мы будем импортировать не модуль стандартной библиотеки, а пакет, который мы только что создали. И давайте напечатаем, что это за объект такой `mypackage`, который мы заимпортировали.\n", + "\n", + "```python\n", + "import mypackage\n", + "\n", + "print(mypackage)\n", + "```\n", + "\n", + "Сохраняем и запускаем с помощью `python3`.\n", + "\n", + "```shell\n", + "python3 mymodule.py\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "! python mymodule.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Мы видим, что у нас импорт прошел успешно, мы не получили никаких ошибок, и видим, что mypackage на самом деле представляет собой модуль. Как я и говорил, пакеты - это тоже модули. Давайте теперь внутри файлика `__init__.py` в модуле `mypackage` напишем немножко кода.\n", + "\n", + "```shell\n", + "vim mypackage/__init__.py\n", + "```\n", + "\n", + "Мы напишем здесь `print(\"importing mypackage\")`. Целью нашей является удостовериться, что файлик `__init__.py` вызывается каждый раз, когда мы импортируем пакет `mypackage` из нашего модуля `mymodule`. Вот мы видим, что на экран напечаталась строка, которую мы написали. Это значит, что файлик `__init__.py` был выполнен при импорте." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "importing mypackage\n", + "\n" + ] + } + ], + "source": [ + "! python mymodule.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте посмотрим на директорию, которая у нас получилась. Мы видим, что мы создали `mymodule.py`, а также пакет, который содержит файлик `__init__.py`.\n", + "\n", + "```shell\n", + "tree\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Однако обратите внимание, что помимо тех директорий и файлов, которые мы создавали сами, Python автоматически создал директорию, которая называется `__pycache__` с двумя подчеркиваниями в начале и в конце, а также файлик `_init_.pyc`. Что это такое? Директория `__pycache__` и файлик с расширением.pyc содержат скомпилированное представление нашего кода. \n", + "\n", + "Виртуальная машина Python не выполняет напрямую код, который мы пишем сами, она прежде всего его преобразовывает в оптимизированное внутреннее представление, которое называется байткодом. Соответственно, директории `__pycache__` и файлы с расширением.pyc содержат как раз такое соптимизированное представление нашего кода, то есть байткод. Про то, что такое байткод и зачем он нужен, мы с вами будем говорить в отдельной лекции, а сейчас просто не пугайтесь, когда вы увидите вот такие автоматически созданные Python'ом файлы." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте пойдем дальше и еще поговорим об импортах и организации кода на Python'е. Откроем наш модуль. Очень часто на практике вы будете видеть вот такую конструкцию.\n", + "\n", + "```python\n", + "if __name__ == \"__main__\":\n", + " print(\"Hello\")\n", + "```\n", + "\n", + "Что это значит? Каждый модуль содержит в своем пространстве имен переменную `__name__` с двумя подчеркиваниями, которая определяет название модуля, в котором выполняется код. Это позволяет разделить те моменты, когда наш модуль используется напрямую интерпретатором Python, либо он был импортирован из другого модуля. Что это значит - лучше посмотреть на примере.\n", + "\n", + "Давайте попробуем выполнить наш модуль напрямую интерпретатором Python. Модуль выполнился, и мы видим, что строка \"hello\" напечаталась на экран." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "importing mypackage\n", + "Hello\n" + ] + } + ], + "source": [ + "! python mymodule.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Однако если мы сейчас зайдем в интерактивный интерпретатор и попробуем заимпортировать наш модуль, мы видим, что строка \"hello\" не напечаталась." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "importing mypackage\n" + ] + } + ], + "source": [ + "import mymodule" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Это как раз благодаря той проверке на то, как вызывается наш модуль, `if __name__ == \"_main__\"`. Мы можем таким образом контролировать, какие кусочки кода в каких условиях у нас вызываются. Далее.\n", + "\n", + "Следующее, что мы посмотрим, это как внутри пакета создавать свои отдельные модули, как я уже сказал, пакет является объединением одного и более модулей, и как оттуда импортировать код. Синтаксис импортов в Python достаточно богат, поэтому мы сейчас это осветим. Давайте создадим внутри директории `mypackage` новый модуль, который будет называться `utils.py`.\n", + "\n", + "```shell\n", + "vim mypackage/utils.py\n", + "```\n", + "\n", + "В этом модуле мы объявим функцию. С функциями вы пока не знакомы, и у нас в следующих блоках будут отдельная лекция, посвященная им. Однако давайте сейчас напишем самую простую функцию, которая будет называться `multiply`, и она будет перемножать два значения, которые ей передали в качестве аргументов, и возвращать результат этого умножения. Функция в Python записывается с помощью ключевого слова `def`, ну вы это еще увидите в дальнейшем.\n", + "\n", + "```python\n", + "def multiply(a, b):\n", + " return a * b\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Пока наша задача - посмотреть, как эту функцию можно заимпортировать, находясь внутри `mymodule`, который мы с вами написали. Давайте посмотрим на структуру, которая у нас получилась. Вот файлик `utils.py`, который мы только что создали, он внутри пакета `mypackage`. Как нам получить функцию `multiply` внутри `mymodule`? На самом деле, все, что нам нужно сделать - это сделать вот такой импорт, `import mypackage.utils`. Теперь мы можем обращаться к функции `multiply` вот таким вот образом - `mypackage.utils.multiply`. Давайте умножим 2 и 3.\n", + "\n", + "```shell\n", + "vim mymodule.py\n", + "```\n", + "\n", + "Сохраним и вызовем `python3`. У нас получилось." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "importing mypackage\n", + "6\n" + ] + } + ], + "source": [ + "! python mymodule.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Мы видим, что в результате выполнения нашей программы, получилось число 6. Однако это достаточно длинная запись. В Python'е есть конструкция `from…import`. Что это значит? Что мы можем переписать наш импорт вот таким образом, используя `from mypackage.utils import`, и дальше название нашей функции, которую мы прописали в модуле `utils`. Тогда в коде мы можем обращаться к этой функции просто по ее имени, потому что она при импорте была добавлена в локальное пространство имен.\n", + "\n", + "```python\n", + "from mypackage.utils import multiply\n", + "\n", + "if __name__ == \"__main__\":\n", + " print(multiply(2, 3))\n", + "```\n", + "\n", + "Опять же запустим - то же самое." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "importing mypackage\n", + "6\n" + ] + } + ], + "source": [ + "! python mymodule.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Есть еще возможность. Есть такой синтаксис, как `import` звездочка. То есть он записывается вот так, `import *`. Это позволяет из модуля `utils` заимпортировать все объявления, которые там содержатся.\n", + "\n", + "```python\n", + "from mypackage.utils import *\n", + "\n", + "multiply(2, 3)\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "importing mypackage\n", + "6\n" + ] + } + ], + "source": [ + "! python mymodule.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "В данном случае у нас там содержится только функция `multiply`. Она будет заимпортирована, и мы можем ее использовать. Запустим - то же самое. Однако такой импорт со звездочкой не рекомендуется к использованию на практике, потому что он неявный. И в большинстве случаев его используют при экспериментах в интерактивном интерпретаторе, возможно при тестировании кода, но не советуют использовать при написании кода приложений и при импортах из каких-то библиотек.\n", + "\n", + "Последнее, что нам осталось посмотреть в работе модулей — это то, как посмотреть, где, например, находится модуль, который мы заимпортировали. Давайте попробуем это сделать. Под словами \"где он находится\" я имею в виду, где на диске. В Python есть такой замечательный модуль, который называется `this`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The Zen of Python, by Tim Peters\n", + "\n", + "Beautiful is better than ugly.\n", + "Explicit is better than implicit.\n", + "Simple is better than complex.\n", + "Complex is better than complicated.\n", + "Flat is better than nested.\n", + "Sparse is better than dense.\n", + "Readability counts.\n", + "Special cases aren't special enough to break the rules.\n", + "Although practicality beats purity.\n", + "Errors should never pass silently.\n", + "Unless explicitly silenced.\n", + "In the face of ambiguity, refuse the temptation to guess.\n", + "There should be one-- and preferably only one --obvious way to do it.\n", + "Although that way may not be obvious at first unless you're Dutch.\n", + "Now is better than never.\n", + "Although never is often better than *right* now.\n", + "If the implementation is hard to explain, it's a bad idea.\n", + "If the implementation is easy to explain, it may be a good idea.\n", + "Namespaces are one honking great idea -- let's do more of those!\n" + ] + } + ], + "source": [ + "import this" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Давайте сделаем `import this`, и видим, что на экран вывелся набор утверждений, которым должен следовать каждый Python-программист. Давайте посмотрим на первые два. `Beautiful is better than ugly` - красивое лучше, чем некрасивое. Логично. Второе - `Explicit is better than implicit` — явное лучше неявного. Это очень важный принцип. Посмотрите на все эти утверждения. Они забавные, интересные и в целом справедливые. Однако напоминаю, что наша цель — посмотреть, откуда же он был заимпортирован. В Python существуют богатые средства интроспекции, которые предоставляет модуль стандартной библиотеки `inspect`. Мы его импортируем и дальше воспользуемся функцией `getfile` из этого модуля, чтобы посмотреть, где находится модуль `this`. Передаем название модуля `this` и видим путь на жестком диске, на котором хранится наш модуль `this.py`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\this.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import inspect\n", + "\n", + "inspect.getfile(this)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "На вашей операционной системе результат будет, скорее всего, другим. Нас в данном случае интересует директория. А какие же еще модули содержатся в этой директории, помимо `this`? Давайте посмотрим. Сделаем импорт из модуля `os` — это модуль стандартной библиотеки, который позволяет работать с операционной системой. И в модуле `os` есть функция `listdir`, которая позволяет получить содержимое той или иной директории. Мы передаем ей наш путь, который мы только что скопировали, и получаем список файлов, которые находятся в этой директории. Посмотрите на этот огромный список." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['abc.py',\n", + " 'aifc.py',\n", + " 'antigravity.py',\n", + " 'argparse.py',\n", + " 'ast.py',\n", + " 'asynchat.py',\n", + " 'asyncio',\n", + " 'asyncore.py',\n", + " 'base64.py',\n", + " 'bdb.py',\n", + " 'binhex.py',\n", + " 'bisect.py',\n", + " 'bz2.py',\n", + " 'calendar.py',\n", + " 'cgi.py',\n", + " 'cgitb.py',\n", + " 'chunk.py',\n", + " 'cmd.py',\n", + " 'code.py',\n", + " 'codecs.py',\n", + " 'codeop.py',\n", + " 'collections',\n", + " 'colorsys.py',\n", + " 'compileall.py',\n", + " 'concurrent',\n", + " 'configparser.py',\n", + " 'contextlib.py',\n", + " 'contextvars.py',\n", + " 'copy.py',\n", + " 'copyreg.py',\n", + " 'cProfile.py',\n", + " 'crypt.py',\n", + " 'csv.py',\n", + " 'ctypes',\n", + " 'curses',\n", + " 'dataclasses.py',\n", + " 'datetime.py',\n", + " 'dbm',\n", + " 'decimal.py',\n", + " 'difflib.py',\n", + " 'dis.py',\n", + " 'distutils',\n", + " 'doctest.py',\n", + " 'dummy_threading.py',\n", + " 'email',\n", + " 'encodings',\n", + " 'ensurepip',\n", + " 'enum.py',\n", + " 'filecmp.py',\n", + " 'fileinput.py',\n", + " 'fnmatch.py',\n", + " 'formatter.py',\n", + " 'fractions.py',\n", + " 'ftplib.py',\n", + " 'functools.py',\n", + " 'genericpath.py',\n", + " 'getopt.py',\n", + " 'getpass.py',\n", + " 'gettext.py',\n", + " 'glob.py',\n", + " 'gzip.py',\n", + " 'hashlib.py',\n", + " 'heapq.py',\n", + " 'hmac.py',\n", + " 'html',\n", + " 'http',\n", + " 'idlelib',\n", + " 'imaplib.py',\n", + " 'imghdr.py',\n", + " 'imp.py',\n", + " 'importlib',\n", + " 'inspect.py',\n", + " 'io.py',\n", + " 'ipaddress.py',\n", + " 'json',\n", + " 'keyword.py',\n", + " 'lib2to3',\n", + " 'libLIEF.dll',\n", + " 'libLIEF.lib',\n", + " 'linecache.py',\n", + " 'locale.py',\n", + " 'logging',\n", + " 'lzma.py',\n", + " 'mailbox.py',\n", + " 'mailcap.py',\n", + " 'mimetypes.py',\n", + " 'modulefinder.py',\n", + " 'msilib',\n", + " 'multiprocessing',\n", + " 'netrc.py',\n", + " 'nntplib.py',\n", + " 'ntpath.py',\n", + " 'nturl2path.py',\n", + " 'numbers.py',\n", + " 'opcode.py',\n", + " 'operator.py',\n", + " 'optparse.py',\n", + " 'os.py',\n", + " 'pathlib.py',\n", + " 'pdb.py',\n", + " 'pickle.py',\n", + " 'pickletools.py',\n", + " 'pipes.py',\n", + " 'pkgutil.py',\n", + " 'platform.py',\n", + " 'plistlib.py',\n", + " 'poplib.py',\n", + " 'posixpath.py',\n", + " 'pprint.py',\n", + " 'profile.py',\n", + " 'pstats.py',\n", + " 'pty.py',\n", + " 'pyclbr.py',\n", + " 'pydoc.py',\n", + " 'pydoc_data',\n", + " 'py_compile.py',\n", + " 'queue.py',\n", + " 'quopri.py',\n", + " 'random.py',\n", + " 're.py',\n", + " 'reprlib.py',\n", + " 'rlcompleter.py',\n", + " 'runpy.py',\n", + " 'sched.py',\n", + " 'secrets.py',\n", + " 'selectors.py',\n", + " 'shelve.py',\n", + " 'shlex.py',\n", + " 'shutil.py',\n", + " 'signal.py',\n", + " 'site-packages',\n", + " 'site.py',\n", + " 'smtpd.py',\n", + " 'smtplib.py',\n", + " 'sndhdr.py',\n", + " 'socket.py',\n", + " 'socketserver.py',\n", + " 'sqlite3',\n", + " 'sre_compile.py',\n", + " 'sre_constants.py',\n", + " 'sre_parse.py',\n", + " 'ssl.py',\n", + " 'stat.py',\n", + " 'statistics.py',\n", + " 'string.py',\n", + " 'stringprep.py',\n", + " 'struct.py',\n", + " 'subprocess.py',\n", + " 'sunau.py',\n", + " 'symbol.py',\n", + " 'symtable.py',\n", + " 'sysconfig.py',\n", + " 'tabnanny.py',\n", + " 'tarfile.py',\n", + " 'telnetlib.py',\n", + " 'tempfile.py',\n", + " 'test',\n", + " 'textwrap.py',\n", + " 'this.py',\n", + " 'threading.py',\n", + " 'timeit.py',\n", + " 'tkinter',\n", + " 'token.py',\n", + " 'tokenize.py',\n", + " 'trace.py',\n", + " 'traceback.py',\n", + " 'tracemalloc.py',\n", + " 'tty.py',\n", + " 'turtle.py',\n", + " 'turtledemo',\n", + " 'types.py',\n", + " 'typing.py',\n", + " 'unittest',\n", + " 'urllib',\n", + " 'uu.py',\n", + " 'uuid.py',\n", + " 'venv',\n", + " 'warnings.py',\n", + " 'wave.py',\n", + " 'weakref.py',\n", + " 'webbrowser.py',\n", + " 'wsgiref',\n", + " 'xdrlib.py',\n", + " 'xml',\n", + " 'xmlrpc',\n", + " 'zipapp.py',\n", + " 'zipfile.py',\n", + " 'zipimport.py',\n", + " '_bootlocale.py',\n", + " '_collections_abc.py',\n", + " '_compat_pickle.py',\n", + " '_compression.py',\n", + " '_dummy_thread.py',\n", + " '_markupbase.py',\n", + " '_nsis.py',\n", + " '_osx_support.py',\n", + " '_pydecimal.py',\n", + " '_pyio.py',\n", + " '_py_abc.py',\n", + " '_sitebuiltins.py',\n", + " '_strptime.py',\n", + " '_system_path.py',\n", + " '_threading_local.py',\n", + " '_weakrefset.py',\n", + " '__future__.py',\n", + " '__phello__.foo.py',\n", + " '__pycache__']" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "\n", + "os.listdir(\"C:\\\\ProgramData\\\\Anaconda3\\\\lib\\\\\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Это все модули стандартной библиотеки в Python, коих просто тонны. Там есть модули на все случаи жизни. И с какой-то частью из этих модулей мы вас познакомим в рамках нашего курса в дальнейшем. Однако может так получиться, что даже этой богатой функциональности, которая есть в стандартной библиотеке Python, вам может не хватить — у вас какая-то специфичная задача. Не отчаивайтесь, Python — это язык с огромным сообществом, и существует масса библиотек, написанных этим сообществом на все случаи жизни, которые вы можете установить в свою систему. Эти библиотеки находятся на ресурсе `pypi.org`, и вы в любой момент можете зайти на этот ресурс, посмотреть, какая библиотека вам нужна, и установить ее в вашу систему. А установка пакета стороннего в систему производиться с помощью утилиты `pip`, и мы с вами на следующей лекции посмотрим, как стороннюю библиотеку в вашу систему можно поставить.\n", + "\n", + "На этой лекции мы познакомились с организацией кода на Python, посмотрели, что такое модули и что такое пакеты, и как их организовывать, как импортировать код из модулей и пакетов. Также с некоторыми особенностями, которые с модулями и пакетами связаны. Нас ждет в дальнейшем еще немало работы с модулями стандартной библиотеки. Теперь вы умеете ими пользоваться." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Объектная структура в Python.ipynb b/1. Введение в Python/4. Организация кода и окружение/Объектная структура в Python.ipynb new file mode 100644 index 0000000..c7d0034 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Объектная структура в Python.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "381820da", + "metadata": {}, + "source": [ + "# Объектная структура в Python #" + ] + }, + { + "cell_type": "markdown", + "id": "405c46bd", + "metadata": {}, + "source": [ + "Сейчас давайте поговорим про объектную структуру в Python.\n", + "\n", + "На протяжении всей недели я подчеркивал, что когда мы создаем переменную, мы не просто присваиваем переменной значение, а мы создаем связь имени переменной с объектом.\n", + "\n", + "Давайте посмотрим, что это значит. Посмотрим на примере целочисленного объекта. Давайте объявим переменную `num` и свяжем ее с целочисленным объектом 13." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "38ec2a88", + "metadata": {}, + "outputs": [], + "source": [ + "num = 13" + ] + }, + { + "cell_type": "markdown", + "id": "479473c5", + "metadata": {}, + "source": [ + "Если посмотреть внимательней, то окажется, что у переменной `num` есть метод `__add__` c двумя подчеркиваниями, который добавляет к числу другое число." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "626c1f15", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "15" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num.__add__(2)" + ] + }, + { + "cell_type": "markdown", + "id": "4585f708", + "metadata": {}, + "source": [ + "Как же так? Что это за метод? В других языках программирования вы могли видеть, что когда вы создаете переменную, эта переменная на самом деле ссылается непосредственно на адрес памяти, в котором хранится, например, число. В Python не так.\n", + "\n", + "В Python переменная ссылается на объект, и если мы посмотрим внимательней на наш объект и используем функцию `dir` для того, чтобы посмотреть все методы атрибута, то мы увидим, что на самом деле их еще больше, чем мы могли бы подумать. Их действительно очень много." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "dd2ae309", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']\n" + ] + } + ], + "source": [ + "print(dir(num))" + ] + }, + { + "cell_type": "markdown", + "id": "77fa3764", + "metadata": {}, + "source": [ + "Если мы то же самое сделаем со строкой, то мы увидим, что и для строки выполняется то же самое." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "05b3e0c9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']\n" + ] + } + ], + "source": [ + "print(dir(\"строка\"))" + ] + }, + { + "cell_type": "markdown", + "id": "c9225a3e", + "metadata": {}, + "source": [ + "Дело в том, что в Python всё есть объект. Целые числа, строки — это все представлено, как объект внутри. На C уровне определена структура, которая называется `PyObject`.\n", + "\n", + "```c\n", + "typedef struct _object {\n", + " _PyObject_HEAD_EXTRA\n", + " Py_ssize_t ob_refcnt; // Счетчик ссылок\n", + " struct _typeobject *ob_type; // Указатель на тип объекта\n", + "} PyObject;\n", + "```\n", + "\n", + "Эта структура содержит два важных поля, первое из которых это `ob_refcnt` — это счетчик ссылок. Что это такое?\n", + "Когда мы создаем новую связь имени переменной с объектом, этот счетчик ссылок автоматически увеличивается на единицу. Как только создается еще одна связь — еще на единицу, и так далее. Когда же связь становится не нужна, например, функция закончила выполнение, и Python понимает, что какая-то переменная больше не нужна, счетчик ссылок, на который она ссылается, на единицу уменьшается, и когда он достигает нуля, счетчик ссылок может удалить объект из памяти. Таким образом, в Python автоматическая сборка мусора со счетчиком ссылок.\n", + "\n", + "Второе важное поле — это указатель на тип объекта. Зачем это нужно? Как мы уже говорили, Python — это язык с динамической типизацией, и указатель на тип объекта, который есть у каждого объекта в Python, позволяет Python'у в процессе работы в runtime'е определять тип того или иного объекта, тем самым производя правильные операции, применяя правильные действия к этому объекту.\n", + "\n", + "Также есть структура `PyVarObject`, которая расширяет структуры `PyObject` и добавляет одно важное поле.\n", + "\n", + "```c\n", + "typedef struct {\n", + " PyObject ob_base;\n", + " Py_ssize_t ob_size; // Кол-во элементов в переменной части\n", + "} PyVarObject;\n", + "```\n", + "\n", + "Это поле `ob_size`. Поле `ob_size` содержит количество элементов переменной части объекта. Что это значит? Это значит, что это поле, например, может использоваться как контейнер для длины строки. Для того чтобы каждый раз не рассчитывать длину строки, не итерироваться по всем символам, длину строки можно сохранять в это поле.\n", + "\n", + "Это поле используется в различных типах по-разному для строки, как мы видим, для хранения длины.\n", + "\n", + "Так же на C уровне определены два макроса, которые соответствуют `PyVarObject` и `PyObject`, и эти макросы используются для того, чтобы создавать новые типы в Python, новые объекты.\n", + "\n", + "```c\n", + "#define PyObject_HEAD PyObject ob_base;\n", + "#define PyObject_VAR_HEAD PyVarObject ob_base;\n", + "\n", + "typedef struct PyMyObject {\n", + " PyObject_HEAD\n", + " ...\n", + "}\n", + "```\n", + "\n", + "или\n", + "\n", + "```c\n", + "typedef struct PyMyObject {\n", + " PyObject_VAR_HEAD\n", + " ...\n", + "}\n", + "```\n", + "\n", + "По сути так реализованы все встроенные типы в Python. Они расширяют структуры `PyObject`, либо `PyVarObject`. Тем самым, у каждого объекта есть счетчик ссылок, у каждого объекта есть указатель на тип.\n", + "\n", + "Что стоит отметить еще, что так реализованы не только какие-то простые типы, такие как `int`, `float`, `boolean` типы, но и другие вещи, которые вы встречаете в Python'е. Например, модули, либо функции, либо классы. Все это является под капотом объектом со своими методами и атрибутами.\n", + "\n", + "Теперь, я думаю, вас не испугает огромное количество методов, которые есть у питоновских объектов. Вы знаете, что на самом деле под капотом все это является расширением структуры `PyObject`, и, что самое главное, из этого следует, то что в Python'е любой объект можно присвоить переменной, можно передать как аргумент в функцию. На самом деле знать это необязательно для того, чтобы программировать на Python'е. Однако это информация, которая поможет вам в дальнейшем стать более продуктивным, все-таки знать, как инструмент устроен. Это очень важно." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Пример. Пишем программу.ipynb b/1. Введение в Python/4. Организация кода и окружение/Пример. Пишем программу.ipynb new file mode 100644 index 0000000..9ddbf8b --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Пример. Пишем программу.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c8270781", + "metadata": {}, + "source": [ + "# Пример. Пишем программу #" + ] + }, + { + "cell_type": "markdown", + "id": "055a2af7", + "metadata": {}, + "source": [ + "Сейчас давайте попробуем обобщить все, что мы прошли и напишем небольшую программку.\n", + "\n", + "Суть ее сводится к тому, что по нашему IP-адресу мы хотим получить информацию о нашем местоположении. При этом мы пройдем все шаги с начала, используя виртуальное окружение и давайте займемся этим.\n", + "\n", + "В Интернете есть замечательный ресурс - `ip-api.com`, который по нашему IP-адресу позволяет посмотреть информацию о нашем местоположении. Доступна эта информация по адресу `ip-api.com/json/`. Мы эту информацию сейчас получим с помощью Python и напечатаем на экран.\n", + "\n", + "```json\n", + "{\n", + " \"query\": \"192.168.0.1\",\n", + " \"status\": \"success\",\n", + " \"countryCode\": \"RU\",\n", + " \"country\": \"Russia\",\n", + " \"region\": \"CHE\",\n", + " \"regionName\": \"Chelyabinsk Oblast\",\n", + " \"city\": \"Snezhinsk\",\n", + " \"zip\": \"456770\",\n", + " \"lat\": 56.0855106,\n", + " \"lon\": 60.7314472,\n", + " \"timezone\": \"Asia/Yekaterinburg\"\n", + "}\n", + "```\n", + "\n", + "Обратите внимание, что здесь есть как раз информация о нашей стране. В зависимости от вашего IP-адреса здесь также может присутствовать и другая информация, вплоть до вашего города, например. Она будет содержаться в поле `city`.\n", + "\n", + "Давайте начнем. Первое что мы сделаем - это создадим директорию, в которой будем работать. Назовем ее также как всегда - `playground`, перейдем в нее и теперь мы можем создать в ней виртуальное окружение `python3 -m venv` и название директории, в которой будет создаваться наше виртуальное окружение. Как только виртуальное окружение создано, мы можем его активировать.\n", + "\n", + "```shell\n", + ". env/bin/activate\n", + "```\n", + "\n", + "Обратите внимание, что точка - это замена команды `source`, которую я показывал ранее. Виртуальное окружение создано, и мы можем установить пакет. Мы установим пакет `requests`. Это библиотека, как я уже говорил, для работы с `http` запросами, причем очень удобная библиотека, которой пользуются практически все питонисты для работы с `http`.\n", + "\n", + "Все успешно установлено. Давайте начнем программировать. Назовем наш файлик `location.py`. Первое, что мы сделаем, это сделаем `import import requests`, который мы будем использовать.\n", + "\n", + "Далее мы хотим чтобы наша программка работала только тогда, когда мы ее запускаем интерпретатором Python, то есть нам нужно написать конструкцию `if __name__ == \"__main__\":`. Внутри этой конструкции мы напечатаем на экран результат выполнения нашей функции, которая будет называться `get_location_info`.\n", + "\n", + "Конечно же нам нужно объявить саму эту функцию. Напомню, что про функции мы вам будем рассказывать более подробно в дальнейшем. В одну строчку мы получим данные с сайта `ip-api.com`, используя библиотеку `requests`. У `requests` есть метод `get` для того, чтобы сделать `get http` запрос, то есть то как мы открывали эту страничку в браузере.\n", + "\n", + "Давайте скопируем адрес, вставим его. Также есть метод `json`, который позволит текст, который возвращается, когда мы запрашиваем этот адрес в формате `json` преобразовать во внутреннее представление в Python. В нашем случае этот `json` будет преобразован в словарь. Словарь - это структура данных, которая встроена в язык и про нее мы также будем рассказывать в дальнейшем. В принципе, все готово.\n", + "\n", + "```python\n", + "import requests\n", + "\n", + "def get_location_info():\n", + " return requests.get(\"http://ip-api.com/json/\").json()\n", + "\n", + "if __name__ == \"__main__\":\n", + " print(get_location_info())\n", + "```\n", + "\n", + "Теперь мы можем запустить нашу программу - `python location.py`. Мы получили эти данные. Все очень быстро, все очень удобно, в одну строчку практически решение. Однако, давайте посмотрим на код. Что здесь не хватает? Конечно же, у нас может отсутствовать Интернет соединение, либо сайт `ip-api.com` может быть недоступен. В таком случае упадет ошибка. Ошибки мы в этой программе не обрабатываем. Однако, на практике, чтобы писать хороший код, вы должны обрабатывать все возможные исключения, которые могут произойти. Этому я вас буду учить в следующих лекциях." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Проверка установки Python.ipynb b/1. Введение в Python/4. Организация кода и окружение/Проверка установки Python.ipynb new file mode 100644 index 0000000..c9d51cd --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Проверка установки Python.ipynb @@ -0,0 +1,49 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "06c03dfd", + "metadata": {}, + "source": [ + "# Проверка установки Python #" + ] + }, + { + "cell_type": "markdown", + "id": "a44cc24d", + "metadata": {}, + "source": [ + "Я хочу удостовериться, что Python 3 верно установлен в систему и вы научились пользоваться виртуальным окружением и ставить внешние модули с помощью утилиты pip.\n", + "\n", + "Для выполнения задания вам нужно:\n", + "\n", + "1. Создать, активировать и деактевировать виртуальное окружение;\n", + "2. Запустить интерпретатор Python 3, и убедиться в его версия >= 3.6;\n", + "3. Импортировать модуль стандартной библиотеки Python;\n", + "4. Установить сторонний модуль, например, `requests`;\n", + "5. Импортировать модуль, который не входит в стандартную библиотеку Python, например, `requests`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Работа offline.ipynb b/1. Введение в Python/4. Организация кода и окружение/Работа offline.ipynb new file mode 100644 index 0000000..052b835 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Работа offline.ipynb @@ -0,0 +1,105 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d6326720", + "metadata": {}, + "source": [ + "# Работа offline #" + ] + }, + { + "cell_type": "markdown", + "id": "30e75439", + "metadata": {}, + "source": [ + "Отсутствие интернета не беда!\n", + "\n", + "В pypi репозитории 300 тысяч пакетов, 2,5 милионов релизов и порядка 4,5 милионов файлов что занимает более 2Тб. - это пакеты в исходном коде, колеса (whl), пакеты под разные платформы, версии Python и разные версии самого пакета.\n", + "\n", + "Для синхронизации репозитория pypi можно использовать пакет `bandersnatch`. Для развертывания локального pypi репозитория есть пакет `pypiserver`. Использовать его легко:\n", + "\n", + "```shell\n", + "pypi-server --port 8080 --interface 127.0.0.1 --root ~/path/packages --disable-fallback\n", + "```\n", + "\n", + "где в директории ~/path/packages находятся пакеты Python. Чтобы использовать данный pypi репозиторий необходимо настроить свой pip:\n", + "\n", + "```ini\n", + "; Linux: /etc/pip.conf\n", + "; Windows: C:\\Users\\User\\AppData\\Roaming\\pip\\pip.ini\n", + "\n", + "[global]\n", + "index = http://127.0.0.1:8080\n", + "index-url = http://127.0.0.1:8080/simple\n", + "trusted-host = 127.0.0.1\n", + "```\n", + "\n", + "Скачать определенный пакет с его зависимостями можно командой:\n", + "\n", + "```shell\n", + "pip download -d ~/path/packages [ ]\n", + "```\n", + "\n", + "Установить пакет из директории можно вот так:\n", + "\n", + "```shell\n", + "pip install --no-index --find-links ~/path/packages [ ]\n", + "```\n", + "\n", + "Для обновления самого pip используют команду:\n", + "\n", + "```shell\n", + "pip install --upgrade pip\n", + "```\n", + "\n", + "или (что в Windows чаще приходится делать):\n", + "```shell\n", + "python -m pip install --upgrade pip\n", + "```\n", + "\n", + "Сам pip можно получить разным способом (если конечно его нет в системе с самим Python):\n", + "\n", + "- установить из репозитория в Linux:\n", + "\n", + "```shell\n", + "yum install python3-pip\n", + "```\n", + "\n", + "- установить с помощью модуля Python:\n", + "\n", + "```shell\n", + "python3 -m ensurepip\n", + "```\n", + "\n", + "- скачать с сайта и установить.\n", + "\n", + "```shell\n", + "wget https://bootstrap.pypa.io/get-pip.py\n", + "python get-pip.py\n", + "```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Рисуем лестницу.ipynb b/1. Введение в Python/4. Организация кода и окружение/Рисуем лестницу.ipynb new file mode 100644 index 0000000..cdbd7c3 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Рисуем лестницу.ipynb @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "caa5f142", + "metadata": {}, + "source": [ + "# Рисуем лестницу #" + ] + }, + { + "cell_type": "markdown", + "id": "cf46f7d9", + "metadata": {}, + "source": [ + "Эта задачка чуть сложней предыдущей и потребует от вас размышлений. Мы будем рисовать лестницу.\n", + "\n", + "На вход ваша программа будет получать количество ступенек.\n", + "\n", + "```python\n", + "import sys\n", + "\n", + "num_steps = int(sys.argv[1])\n", + "```\n", + "\n", + "Ваша цель напечатать на экран лесенку используя символы пробела \" \" и решетки \"#\". Например, для входного параметра (количества ступенек) 4 лесенка должна выглядеть следующим образом:\n", + "\n", + "```\n", + " #\n", + " ##\n", + " ###\n", + "####\n", + "```\n", + "\n", + "Конечно, мы будем подавать на вход вашей программе разное количество ступенек." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "9d3cc6ed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " #\n", + " ##\n", + " ###\n", + " ####\n", + " #####\n" + ] + } + ], + "source": [ + "! python stairs.py 5" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Сумма цифр в строке.ipynb b/1. Введение в Python/4. Организация кода и окружение/Сумма цифр в строке.ipynb new file mode 100644 index 0000000..bc73d78 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Сумма цифр в строке.ipynb @@ -0,0 +1,83 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "749960d7", + "metadata": {}, + "source": [ + "# Сумма цифр в строке #" + ] + }, + { + "cell_type": "markdown", + "id": "e18f4190", + "metadata": {}, + "source": [ + "Давайте начнем с несложной задачки - ваша цель найти сумму цифр, из которых состоит строка.\n", + "\n", + "Вы должны создать Python модуль `solution.py`. Этот файл запускаем с аргументом командной строки – строкой, состоящей из цифр. Например, вот такой:\n", + "\n", + "\"873\"\n", + "\n", + "Чтобы получить ввод, вы в начале программы можете считать его, используя модуль стандартной библиотеки sys:\n", + "\n", + "```python\n", + "import sys\n", + "\n", + "digit_string = sys.argv[1]\n", + "```\n", + "\n", + "В переменной `digit_string` будет содержаться строка \"873\" (ну или какая-то другая строка, в том числе другой длины). В строке, подаваемой на вход, будут только символы, соответствующие цифрам от 0 до 9.\n", + "\n", + "В результате ваша программа должна напечатать на экран сумму цифр (для строки \"873\" сумма будет 18).\n", + "\n", + "То, что полученная программа ведет себя должным образом можно проверить локально, запустив ее следующим образом:\n", + "\n", + "```python\n", + "python3 solution.py 873\n", + "```\n", + "\n", + "В списке `sys.argv` будут лежать аргументы командной строки, `sys.argv[0]` - имя запущенного файла, `sys.argv[1]` - строка, сумму цифр которой необходимо посчитать и вывести на экран." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "38669063", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "18\n" + ] + } + ], + "source": [ + "! python solution.py 873" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Тест по блоку (Clear).ipynb b/1. Введение в Python/4. Организация кода и окружение/Тест по блоку (Clear).ipynb new file mode 100644 index 0000000..23cc6c3 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Тест по блоку (Clear).ipynb @@ -0,0 +1,230 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "386445d7", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "75a76c51", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "67365d05", + "metadata": {}, + "source": [ + "##### 1. Совместимы ли Python 2 и Python 3?\n", + "\n", + "- [ ] Несовместимы\n", + "- [ ] Совместимы полностью" + ] + }, + { + "cell_type": "markdown", + "id": "e9415f9e", + "metadata": {}, + "source": [ + "##### 2. На каком языке программирования написана основная реализация спецификации Python?\n", + "\n", + "- [ ] Python\n", + "- [ ] C\n", + "- [ ] Java" + ] + }, + { + "cell_type": "markdown", + "id": "02e8631d", + "metadata": {}, + "source": [ + "##### 3. Какое расширение обычно дают файлам с кодом на Python?\n", + "\n", + "- [ ] .python\n", + "- [ ] .pyc\n", + "- [ ] .py" + ] + }, + { + "cell_type": "markdown", + "id": "c62d7c53", + "metadata": {}, + "source": [ + "##### 4. Как пишутся комментарии в Python?\n", + "\n", + "- [ ] `/* это комментарий */`\n", + "- [ ] `# это комментарий`\n", + "- [ ] `// это комментарий`" + ] + }, + { + "cell_type": "markdown", + "id": "ba1df828", + "metadata": {}, + "source": [ + "##### 5. Какие имена переменных правильные?\n", + "\n", + "- [ ] name\n", + "- [ ] _name\n", + "- [ ] !name\n", + "- [ ] 1name" + ] + }, + { + "cell_type": "markdown", + "id": "0720dc5e", + "metadata": {}, + "source": [ + "##### 6. `bool(0.000001)` - `True` или `False`?\n", + "\n", + "- [ ] False\n", + "- [ ] True" + ] + }, + { + "cell_type": "markdown", + "id": "0db8f448", + "metadata": {}, + "source": [ + "##### 7. Что получится в результате выполнения среза `[0:1]` для строки \"привет\"?\n", + "\n", + "- [ ] \"п\"\n", + "- [ ] \"пр\"\n", + "- [ ] \"\"" + ] + }, + { + "cell_type": "markdown", + "id": "e3137721", + "metadata": {}, + "source": [ + "##### 8. Какая функция позволяет считать ввод пользователя из терминала?\n", + "\n", + "- [ ] input()\n", + "- [ ] readline()\n", + "- [ ] enter()\n", + "- [ ] read()" + ] + }, + { + "cell_type": "markdown", + "id": "b42cb89d", + "metadata": {}, + "source": [ + "##### 9. Какой метод превратит байтовую строку в строку?\n", + "\n", + "- [ ] .decode()\n", + "- [ ] .encode()" + ] + }, + { + "cell_type": "markdown", + "id": "0085afce", + "metadata": {}, + "source": [ + "##### 10. Чему будет равен `pi_fmt`?\n", + "\n", + "```python\n", + "pi = 3.1415926\n", + "pi_fmt = f\"{pi:#0.2f}\"\n", + "```\n", + "\n", + "- [ ] Строке \"3.14\"\n", + "- [ ] Числу 3.14" + ] + }, + { + "cell_type": "markdown", + "id": "7ecf728d", + "metadata": {}, + "source": [ + "##### 11. Предположим, есть пакет **foo**, в котором находится модуль **bar.py**, внутри **bar.py** определена функция с именем **run**. Какая конструкция импорта является правильной?\n", + "\n", + "- [ ] `import run from foo.bar`\n", + "- [ ] `from foo.bar import run`\n", + "- [ ] `import foo.bar.run`" + ] + }, + { + "cell_type": "markdown", + "id": "e7cd2fd1", + "metadata": {}, + "source": [ + "##### 12. Предположим, есть код\n", + "\n", + "```python\n", + "import this\n", + "import this\n", + "```\n", + "\n", + "Сколько раз напечатаются идиомы Python?\n", + "\n", + "- [ ] Дважды\n", + "- [ ] Только единожды" + ] + }, + { + "cell_type": "markdown", + "id": "4ce5fcfb", + "metadata": {}, + "source": [ + "##### 13. Зачем нужен virtualenv (виртуальное окружение)?\n", + "\n", + "- [ ] Возможность увеличить скорость запуска скомпилированных в байткод Python-программ\n", + "- [ ] Возможность запускать несколько интерпретаторов Python одновременно\n", + "- [ ] Изоляция зависимостей" + ] + }, + { + "cell_type": "markdown", + "id": "6d0683f2", + "metadata": {}, + "source": [ + "##### 14. Какая утилита позволяет ставить внешние Python пакеты в систему?\n", + "\n", + "- [ ] pypi\n", + "- [ ] pip\n", + "- [ ] pep" + ] + }, + { + "cell_type": "markdown", + "id": "df78b46f", + "metadata": {}, + "source": [ + "##### 15. Что содержат файлы с расширением `.pyc`?\n", + "\n", + "- [ ] Код на Python, cкомпилированный в машинный код\n", + "- [ ] Код на Python, cкомпилированный в байткод" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Тест по блоку.ipynb b/1. Введение в Python/4. Организация кода и окружение/Тест по блоку.ipynb new file mode 100644 index 0000000..8636b64 --- /dev/null +++ b/1. Введение в Python/4. Организация кода и окружение/Тест по блоку.ipynb @@ -0,0 +1,221 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "386445d7", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "67365d05", + "metadata": {}, + "source": [ + "1. Совместимы ли Python 2 и Python 3?\n", + "\n", + "- [x] Несовместимы\n", + "- [ ] Совместимы полностью" + ] + }, + { + "cell_type": "markdown", + "id": "e9415f9e", + "metadata": {}, + "source": [ + "2. На каком языке программирования написана основная реализация спецификации Python?\n", + "\n", + "- [ ] Python\n", + "- [x] C\n", + "- [ ] Java" + ] + }, + { + "cell_type": "markdown", + "id": "02e8631d", + "metadata": {}, + "source": [ + "3. Какое расширение обычно дают файлам с кодом на Python?\n", + "\n", + "- [ ] .python\n", + "- [ ] .pyc\n", + "- [x] .py" + ] + }, + { + "cell_type": "markdown", + "id": "c62d7c53", + "metadata": {}, + "source": [ + "4. Как пишутся комментарии в Python?\n", + "\n", + "- [ ] `/* это комментарий */`\n", + "- [x] `# это комментарий`\n", + "- [ ] `// это комментарий`" + ] + }, + { + "cell_type": "markdown", + "id": "ba1df828", + "metadata": {}, + "source": [ + "5. Какие имена переменных правильные?\n", + "\n", + "- [x] name\n", + "- [x] _name\n", + "- [ ] !name\n", + "- [ ] 1name" + ] + }, + { + "cell_type": "markdown", + "id": "0720dc5e", + "metadata": {}, + "source": [ + "6. `bool(0.000001)` - `True` или `False`?\n", + "\n", + "- [ ] False\n", + "- [x] True" + ] + }, + { + "cell_type": "markdown", + "id": "0db8f448", + "metadata": {}, + "source": [ + "7. Что получится в результате выполнения среза `[0:1]` для строки \"привет\"?\n", + "\n", + "- [x] \"п\"\n", + "- [ ] \"пр\"\n", + "- [ ] \"\"" + ] + }, + { + "cell_type": "markdown", + "id": "e3137721", + "metadata": {}, + "source": [ + "8. Какая функция позволяет считать ввод пользователя из терминала?\n", + "\n", + "- [x] input()\n", + "- [ ] readline()\n", + "- [ ] enter()\n", + "- [ ] read()" + ] + }, + { + "cell_type": "markdown", + "id": "b42cb89d", + "metadata": {}, + "source": [ + "9. Какой метод превратит байтовую строку в строку?\n", + "\n", + "- [x] .decode()\n", + "- [ ] .encode()" + ] + }, + { + "cell_type": "markdown", + "id": "0085afce", + "metadata": {}, + "source": [ + "10. Чему будет равен `pi_fmt`?\n", + "\n", + "```python\n", + "pi = 3.1415926\n", + "pi_fmt = f\"{pi:#0.2f}\"\n", + "```\n", + "\n", + "- [x] Строке \"3.14\"\n", + "- [ ] Числу 3.14" + ] + }, + { + "cell_type": "markdown", + "id": "7ecf728d", + "metadata": {}, + "source": [ + "11. Предположим, есть пакет **foo**, в котором находится модуль **bar.py**, внутри **bar.py** определена функция с именем **run**. Какая конструкция импорта является правильной?\n", + "\n", + "- [ ] `import run from foo.bar`\n", + "- [x] `from foo.bar import run`\n", + "- [ ] `import foo.bar.run`" + ] + }, + { + "cell_type": "markdown", + "id": "e7cd2fd1", + "metadata": {}, + "source": [ + "12. Предположим, есть код\n", + "\n", + "```python\n", + "import this\n", + "import this\n", + "```\n", + "\n", + "Сколько раз напечатаются идиомы Python?\n", + "\n", + "- [ ] Дважды\n", + "- [x] Только единожды" + ] + }, + { + "cell_type": "markdown", + "id": "4ce5fcfb", + "metadata": {}, + "source": [ + "13. Зачем нужен virtualenv (виртуальное окружение)?\n", + "\n", + "- [ ] Возможность увеличить скорость запуска скомпилированных в байткод Python-программ\n", + "- [ ] Возможность запускать несколько интерпретаторов Python одновременно\n", + "- [x] Изоляция зависимостей" + ] + }, + { + "cell_type": "markdown", + "id": "6d0683f2", + "metadata": {}, + "source": [ + "14. Какая утилита позволяет ставить внешние Python пакеты в систему?\n", + "\n", + "- [ ] pypi\n", + "- [x] pip\n", + "- [ ] pep" + ] + }, + { + "cell_type": "markdown", + "id": "df78b46f", + "metadata": {}, + "source": [ + "15. Что содержат файлы с расширением `.pyc`?\n", + "\n", + "- [ ] Код на Python, cкомпилированный в машинный код\n", + "- [x] Код на Python, cкомпилированный в байткод" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/1. Введение в Python/4. Организация кода и окружение/Тест по блоку.pdf b/1. Введение в Python/4. Организация кода и окружение/Тест по блоку.pdf new file mode 100644 index 0000000..40ff252 Binary files /dev/null and b/1. Введение в Python/4. Организация кода и окружение/Тест по блоку.pdf differ diff --git a/1. Введение в Python/Readme.ipynb b/1. Введение в Python/Readme.ipynb new file mode 100644 index 0000000..2192947 --- /dev/null +++ b/1. Введение в Python/Readme.ipynb @@ -0,0 +1,179 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Введение в Python #\n", + "\n", + "В первом блоке вы познакомитесь с языком, основными конструкциями и базовыми типами. Настроите окружение для работы и выберете среду разработки." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Задачи обучения ##\n", + "\n", + "- Развернуть окружение для программирования на Python.\n", + "- Узнать базовые сведения о языке.\n", + "- Освоить базовые типы и конструкции языка.\n", + "- Узнать об организации кода на Python.\n", + "- Получить начальные сведения о внутреннем устройстве интерпретатора Python." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Оглавление ##" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Знакомство с курсом ###\n", + "\n", + "- [Приветствие](1.%20Знакомство%20с%20курсом/Приветствие.ipynb)\n", + "- [Опрос](1.%20Знакомство%20с%20курсом/Опрос.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Первые шаги ###\n", + "\n", + "- [О языке](2.%20Первые%20шаги/О%20языке.ipynb)\n", + "- [Установка Python 3](2.%20Первые%20шаги/Установка%20Python%203.ipynb)\n", + "- [Работа в терминале](2.%20Первые%20шаги/Работа%20в%20терминале.ipynb)\n", + "- [Выбор среды разработки](2.%20Первые%20шаги/Выбор%20среды%20разработки.ipynb)\n", + "- [Начинаем программировать](2.%20Первые%20шаги/Начинаем%20программировать.ipynb)\n", + "- [Полезные ссылки](2.%20Первые%20шаги/Полезные%20ссылки.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Базовые типы и конструкции ###\n", + "\n", + "- [Базовые типы: численные типы](3.%20Базовые%20типы%20и%20конструкции/Численные%20типы.ipynb)\n", + "- [Базовые типы: логический тип](3.%20Базовые%20типы%20и%20конструкции/Логический%20тип.ipynb)\n", + "- [Базовые типы: строки и байтовые строки](3.%20Базовые%20типы%20и%20конструкции/Строки%20и%20байтовые%20строки.ipynb)\n", + "- [Базовые типы: объект None](3.%20Базовые%20типы%20и%20конструкции/Объект%20None.ipynb)\n", + "- [Конструкции управления потоком](3.%20Базовые%20типы%20и%20конструкции/Конструкции%20управления%20потоком.ipynb)\n", + "- [Пример на управление потоком](3.%20Базовые%20типы%20и%20конструкции/Пример%20на%20управление%20потоком.ipynb)\n", + "- [Тест на типы и конструкции](3.%20Базовые%20типы%20и%20конструкции/Тест%20на%20типы%20и%20конструкции.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Организация кода и окружение ###\n", + "\n", + "- [Модули и пакеты](4.%20Организация%20кода%20и%20окружение/Модули%20и%20пакеты.ipynb)\n", + "- [Виртуальное окружение](4.%20Организация%20кода%20и%20окружение/Виртуальное%20окружение.ipynb)\n", + "- [Пример. Пишем программу](4.%20Организация%20кода%20и%20окружение/Пример.%20Пишем%20программу.ipynb)\n", + "- [Тест по блоку](4.%20Организация%20кода%20и%20окружение/Тест%20по%20блоку.ipynb)\n", + "- [Проверка установки Python](4.%20Организация%20кода%20и%20окружение/Проверка%20установки%20Python.ipynb)\n", + "- [Сумма цифр в строке](4.%20Организация%20кода%20и%20окружение/Сумма%20цифр%20в%20строке.ipynb)\n", + "- [Рисуем лестницу](4.%20Организация%20кода%20и%20окружение/Рисуем%20лестницу.ipynb)\n", + "- [Корни квадратного уравнения](4.%20Организация%20кода%20и%20окружение/Корни%20квадратного%20уравнения.ipynb)\n", + "- [Объектная структура в Python](4.%20Организация%20кода%20и%20окружение/Объектная%20структура%20в%20Python.ipynb)\n", + "- [Байткод](4.%20Организация%20кода%20и%20окружение/Байткод.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Вот и подошел к концу первый блок нашего курса." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Мы с вами познакомились с языком программирования Python, посмотрели на интерактивный интерпретатор, а также познакомились с основными конструкциями и типами, которые есть в языке.\n", + "\n", + "Также мы взглянули на то, как организовывать код на Python-e.\n", + "\n", + "Теперь вы можете писать свои первые простые программы.\n", + "\n", + "Надеюсь вы заметили, насколько Python выразителен и прост." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "В дальнейшем вас ждет еще много всего увлекательного, в том числе в следующем блоке я буду знакомить вас с основными структурами данных, которые есть в языке, а также функциями. [Далее...](../2.%20Структуры%20данных%20и%20функции/Readme.ipynb)" + ] + } + ], + "metadata": { + "celltoolbar": "Слайд-шоу", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Документация.ipynb b/2. Структуры данных и функции/1. Коллекции/Документация.ipynb new file mode 100644 index 0000000..2c1b26e --- /dev/null +++ b/2. Структуры данных и функции/1. Коллекции/Документация.ipynb @@ -0,0 +1,45 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "be6131f1", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "88d93f85", + "metadata": {}, + "source": [ + "Здесь и далее будет много ссылок на стандартную документацию. Умение находить и читать материалы на английском языке является ключевым навыком программиста. Тем не менее, при желании вы всегда можете найти аналогичные статьи и видео на русском языке.\n", + "\n", + "- [Стандартные типы в Python](https://docs.python.org/3/library/stdtypes.html \"4. Built-in Types\")\n", + "- [Туториал по коллекциям из документации](https://docs.python.org/3/tutorial/datastructures.html \"5. Data Structures\")\n", + "- [Hash table](https://en.wikipedia.org/wiki/Hash_table \"Hash table\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Множества. Пример программы.ipynb b/2. Структуры данных и функции/1. Коллекции/Множества. Пример программы.ipynb new file mode 100644 index 0000000..e93ea3f --- /dev/null +++ b/2. Структуры данных и функции/1. Коллекции/Множества. Пример программы.ipynb @@ -0,0 +1,113 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0a39e911", + "metadata": {}, + "source": [ + "# Множества. Пример программы #" + ] + }, + { + "cell_type": "markdown", + "id": "f399fcc2", + "metadata": {}, + "source": [ + "Итак, мы с вами разобрали множества, давайте попробуем решить задачу на их применение.\n", + "\n", + "В качестве примера попробуем выяснить, через сколько итераций функция `random.randint` выдаст повтор. Как вы уже знаете, `randint` возвращает какое-то значение случайное в интервале ему переданном. Давайте импортируем наш модуль `random`, который мы будем использовать, и определим наш `random_set`, в который мы будем складывать наши уникальные числа." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f34fe0ea", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "\n", + "random_set = set()" + ] + }, + { + "cell_type": "markdown", + "id": "8367c37e", + "metadata": {}, + "source": [ + "Теперь воспользуемся циклом `while True` — бесконечным циклом, и в цикле попробуем класть элементы в наше множество. Итак, получим уникальный, какой-то случайный номер, `random.randint(1, 10)`. От одного до десяти. Теперь попробуем проверить, есть ли наш новый номер в множестве. Делаем мы это с помощью оператора `in`. И если этот номер уже содержится в множестве, нам нужно просто выйти, потому что очевидно, именно это мы и хотели выяснить. Если номера нет, мы добавляем наш новый номер в `random_set`. Делаем мы это с помощью знакомого вам метода `add`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "47385363", + "metadata": {}, + "outputs": [], + "source": [ + "while True:\n", + " new_number = random.randint(1, 10)\n", + " \n", + " if new_number in random_set:\n", + " break\n", + " \n", + " random_set.add(new_number)" + ] + }, + { + "cell_type": "markdown", + "id": "d81a9e34", + "metadata": {}, + "source": [ + "И что же теперь происходит? У нас произошло какое-то количество итераций, и в `random_set`'е содержится какой-то набор уникальных объектов. Нашим ответом будет число уникальных объектов в `random_set`'е. Число объектов в `random_set`'е можно получить с помощью знакомой вам встроенной функции `len`. И какая-то еще одна итерация, потому что мы успели выйти до того, как добавили номер повторный. Давайте выведем." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "bb40c926", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6\n" + ] + } + ], + "source": [ + "print(len(random_set) + 1)" + ] + }, + { + "cell_type": "markdown", + "id": "cf5abb51", + "metadata": {}, + "source": [ + "Оказалось, что уже спустя четыре итерации у нас произошел повтор, то есть `random` не такой уж и рандомный. Попробуем запустить еще раз. Да, результат получше. 6 уже больше похоже на то, что мы хотели ожидать. Итак, мы разобрали с вами задачку на множества." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Множества.ipynb b/2. Структуры данных и функции/1. Коллекции/Множества.ipynb new file mode 100644 index 0000000..56d76b2 --- /dev/null +++ b/2. Структуры данных и функции/1. Коллекции/Множества.ipynb @@ -0,0 +1,349 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c8ab769e", + "metadata": {}, + "source": [ + "# Множества #" + ] + }, + { + "cell_type": "markdown", + "id": "448cade2", + "metadata": {}, + "source": [ + "Следующей структурой данных, о которой мы с вами поговорим, являются множества.\n", + "\n", + "Множества позволяют вам хранить в неупорядоченном виде набор уникальных объектов. Чтобы определить пустое множество, можно просто воспользоваться литералом `set`, определить `empty_set`, или использовать фигурные скобочки, чтобы определить `number_set`, в котором уже содержатся какие-то числа." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a6f9836a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{1, 2, 3, 4, 5}\n" + ] + } + ], + "source": [ + "empty_set = set()\n", + "number_set = {1, 2, 3, 3, 4, 5}\n", + "\n", + "print(number_set)" + ] + }, + { + "cell_type": "markdown", + "id": "1f6f1403", + "metadata": {}, + "source": [ + "Обратите внимание, мы пытаемся с вами добавить в `number_set` две тройки и у нас ничего не получится, потому что Python гарантирует вам то, что в множестве содержатся уникальные элементы.\n", + "\n", + "Это достигается с помощью уже знакомой функции хеширования. Также очень легко и быстро проверить, содержится ли какое-то значение в нашем множестве. Делается это с помощью оператора `in`, и проверка тоже выполняется за константное время. \n", + "\n", + "Например, двойка, очевидно, содержится в нашем сете." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "77fec4ef", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "2 in number_set" + ] + }, + { + "cell_type": "markdown", + "id": "31bc07a9", + "metadata": {}, + "source": [ + "Давайте создадим два множества, `odd_set` и `even_set`, в котором будут содержаться значения, чётное и нечётное число от 0 до 10. Делаем мы это с помощью цикла, и в цикле добавляем элементы множества. Множества являются изменяемой структурой данных, поэтому мы можем это делать с помощью встроенного метода `add`. Мы проверяем чётное и нечётное число и добавляем это число в соответствующее множество." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d44dcf28", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{1, 3, 5, 7, 9}\n", + "{0, 2, 4, 6, 8}\n" + ] + } + ], + "source": [ + "odd_set = set()\n", + "even_set = set()\n", + "\n", + "for number in range(10):\n", + " if number % 2:\n", + " odd_set.add(number)\n", + " else:\n", + " even_set.add(number)\n", + " \n", + "print(odd_set)\n", + "print(even_set)" + ] + }, + { + "cell_type": "markdown", + "id": "bc80a0b8", + "metadata": {}, + "source": [ + "Множества, как и предполагает их название, поддерживают математические операции над множествами. Например, мы можем использовать объединения, знакомые вам из курса математики, и получить все значения, которые содержатся как в чётном множестве, так и в нечётном множестве.\n", + "\n", + "Очевидно, это все значения, потому что все числа от 0 до 10 являются либо чётными, либо нечётными." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "fcf03b2b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}\n" + ] + } + ], + "source": [ + "union_set = odd_set | even_set\n", + "union_set = odd_set.union(even_set)\n", + "\n", + "print(union_set)" + ] + }, + { + "cell_type": "markdown", + "id": "48e027f6", + "metadata": {}, + "source": [ + "Точно так же мы можем брать пересечение множеств. Очевидно, что пересечение чётных и нечётных чисел от 0 до 10, да и любых чётных и нечётных чисел - это пустое множество." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "afae36d1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "set()\n" + ] + } + ], + "source": [ + "intersection_set = odd_set & even_set\n", + "intersection_set = odd_set.intersection(even_set)\n", + "\n", + "print(intersection_set)" + ] + }, + { + "cell_type": "markdown", + "id": "e62260b6", + "metadata": {}, + "source": [ + "Также мы можем использовать разность из теории множеств и получить все значения, которые содержатся в одном множестве, но не содержатся в другом множестве. Делается это с помощью оператора минус или встроенного метода `difference`. Очевидно, что в множестве `odd_set` содержатся все элементы, которые не содержатся в множестве `even_set`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "af062911", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{1, 3, 5, 7, 9}\n" + ] + } + ], + "source": [ + "difference_set = odd_set - even_set\n", + "difference_set = odd_set.difference(even_set)\n", + "\n", + "print(difference_set)" + ] + }, + { + "cell_type": "markdown", + "id": "d8117277", + "metadata": {}, + "source": [ + "Существует также симметрическая разность, которую можно получить с помощью шапочки или с помощью метода `symmetric_difference`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f594748a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}\n" + ] + } + ], + "source": [ + "symmetric_difference_set = odd_set ^ even_set\n", + "symmetric_difference_set = odd_set.symmetric_difference(even_set)\n", + "\n", + "print(symmetric_difference_set)" + ] + }, + { + "cell_type": "markdown", + "id": "ddfb3604", + "metadata": {}, + "source": [ + "Так как множества являются изменяемой структурой данных, мы можем не только добавлять туда элементы, но и удалять их. Делается это с помощью метода `remove`, которому мы передаем значение, которое мы хотим удалить из множества. Например, мы можем удалить 2." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "dec76d51", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{0, 4, 6, 8}\n" + ] + } + ], + "source": [ + "even_set.remove(2)\n", + "\n", + "print(even_set)" + ] + }, + { + "cell_type": "markdown", + "id": "d4514890", + "metadata": {}, + "source": [ + "Если мы хотим удалить какое-то случайное значение, можно использовать метод `pop`, который просто вернет какое-то значение из нашего множества. Мы точно не знаем какое." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c1d74063", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "even_set.pop()" + ] + }, + { + "cell_type": "markdown", + "id": "35a57ceb", + "metadata": {}, + "source": [ + "Так как множества являются изменяемой структурой данных, очевидно, у них существует аналог, который неизменяем. Существует `frozenset`, который гарантирует вам уникальность элементов и ведет себя точно так же, как и множества, но мы не можем добавлять туда элементы или удалять их. Например, мы можем определить множество `frozen` и попытаться в него добавить `Olaf'а`. У нас ничего не выйдет, потому что `frozenset` неизменяемый, у него нет метода `add`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "d393d0ec", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'frozenset' object has no attribute 'add'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mfrozen\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mfrozenset\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"Anna\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"Elsa\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"Kristoff\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mfrozen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0madd\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Olaf\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m: 'frozenset' object has no attribute 'add'" + ] + } + ], + "source": [ + "frozen = frozenset([\"Anna\", \"Elsa\", \"Kristoff\"])\n", + "\n", + "frozen.add(\"Olaf\")" + ] + }, + { + "cell_type": "markdown", + "id": "f074172b", + "metadata": {}, + "source": [ + "Итак, мы с вами обсудили множества, которые являются неупорядоченным набором уникальных объектов. А также множества можно изменять, то есть добавлять и удалять оттуда элементы. Мы легко можем проверить, существует ли какое-то значение в нашем множестве с помощью оператора `in`; также множества поддерживают операции над множествами, знакомые вам из курса математики. Давайте попробуем решить задачку на множества." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Словари. Пример программы.ipynb b/2. Структуры данных и функции/1. Коллекции/Словари. Пример программы.ipynb new file mode 100644 index 0000000..713f223 --- /dev/null +++ b/2. Структуры данных и функции/1. Коллекции/Словари. Пример программы.ipynb @@ -0,0 +1,334 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9c7c1cc4", + "metadata": {}, + "source": [ + "# Словари. Пример программы #" + ] + }, + { + "cell_type": "markdown", + "id": "e4b389fd", + "metadata": {}, + "source": [ + "Итак, мы с вами разобрали словари и то, как они работают, и давайте попробуем решить задачку на их применение.\n", + "\n", + "В качестве примера попробуем найти три самых часто встречающихся слова в Zen of Python. Как вы уже знаете, Zen of Python можно получить, импортировав `this`, и в нём содержится какой-то набор утверждений, которым должен следовать программист, который пишет на Python.\n", + "\n", + "Например, красивое лучше чем некрасивое." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e74604f2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The Zen of Python, by Tim Peters\n", + "\n", + "Beautiful is better than ugly.\n", + "Explicit is better than implicit.\n", + "Simple is better than complex.\n", + "Complex is better than complicated.\n", + "Flat is better than nested.\n", + "Sparse is better than dense.\n", + "Readability counts.\n", + "Special cases aren't special enough to break the rules.\n", + "Although practicality beats purity.\n", + "Errors should never pass silently.\n", + "Unless explicitly silenced.\n", + "In the face of ambiguity, refuse the temptation to guess.\n", + "There should be one-- and preferably only one --obvious way to do it.\n", + "Although that way may not be obvious at first unless you're Dutch.\n", + "Now is better than never.\n", + "Although never is often better than *right* now.\n", + "If the implementation is hard to explain, it's a bad idea.\n", + "If the implementation is easy to explain, it may be a good idea.\n", + "Namespaces are one honking great idea -- let's do more of those!\n" + ] + } + ], + "source": [ + "import this" + ] + }, + { + "cell_type": "markdown", + "id": "77beacdd", + "metadata": {}, + "source": [ + "Давайте создадим нашу переменную `zen`, в которую положим эти замечательные утверждения. Мы должны использовать тройные кавычки, потому что здесь есть переносы строк. Скопируем все строки и поместим в `zen`. Отлично." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f7b07b1f", + "metadata": {}, + "outputs": [], + "source": [ + "zen = \"\"\"Beautiful is better than ugly.\n", + "Explicit is better than implicit.\n", + "Simple is better than complex.\n", + "Complex is better than complicated.\n", + "Flat is better than nested.\n", + "Sparse is better than dense.\n", + "Readability counts.\n", + "Special cases aren't special enough to break the rules.\n", + "Although practicality beats purity.\n", + "Errors should never pass silently.\n", + "Unless explicitly silenced.\n", + "In the face of ambiguity, refuse the temptation to guess.\n", + "There should be one-- and preferably only one --obvious way to do it.\n", + "Although that way may not be obvious at first unless you're Dutch.\n", + "Now is better than never.\n", + "Although never is often better than *right* now.\n", + "If the implementation is hard to explain, it's a bad idea.\n", + "If the implementation is easy to explain, it may be a good idea.\n", + "Namespaces are one honking great idea -- let's do more of those!\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "34a73654", + "metadata": {}, + "source": [ + "Теперь нам нужно каким-то образом выяснить, какие слова используются чаще всего. Логично нам нужно для начала разбить нашу большую длинную строчку на, собственно, эти самые слова. \n", + "\n", + "Давайте используем для этого переменную `zen_map` какой-то `dict`, где мы будем хранить собственно количество слов, которое мы уже нашли, и будем итерироваться с помощью метода `split`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "23b04337", + "metadata": {}, + "outputs": [], + "source": [ + "zen_map = dict()" + ] + }, + { + "cell_type": "markdown", + "id": "36889d1e", + "metadata": {}, + "source": [ + "Метод `split` разобьёт нашу большую строку по пробельным символам. У нас получится какое-то слово при итерации. Теперь, что нам нужно сделать с этим словом? Нам нужно, во-первых, его очистить от каких-то точек, от каких-то восклицательных знаков и прочих спецсимволов. Давайте назовём переменную `cleaned_word` и будем очищать наше слово, используя метод строки `strip`.\n", + "\n", + "Очистим всё, что мы можем найти. Отлично. Теперь давайте приведём строку к нижнему регистру с помощью метода `lower`. И теперь нам нужно добавить нашу строку в наш `zen_map`.\n", + "\n", + "Если строка уже есть в `zen_map`'е, мы просто хотим инкрементировать `counter`, который говорит о том, сколько раз мы находили уже это слово. Если строки там нет, то мы туда должны её добавить, и давайте именно это и сделаем.\n", + "\n", + "Если `cleaned_word`'а ещё нету в `zen_map`'е, мы добавим наш `zen_map` в `cleaned_word`. В качестве значения у нас будет 0, потому что мы это слово ещё не встречали. Если мы его встречали или только что добавили, мы инкрементируем это значение.\n", + "\n", + "Таким образом, в нашем `zen_map`'e будет хранится отображение из слова в количество раз, в котором мы его встретили." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4c71d106", + "metadata": {}, + "outputs": [], + "source": [ + "for word in zen.split():\n", + " cleaned_word = word.strip(\".,!-*\").lower()\n", + " \n", + " if cleaned_word not in zen_map:\n", + " zen_map[cleaned_word] = 0\n", + " \n", + " zen_map[cleaned_word] += 1" + ] + }, + { + "cell_type": "markdown", + "id": "16f875bd", + "metadata": {}, + "source": [ + "Давайте попробуем вывести наш `zen_map`. Отлично." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "41ca869f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'beautiful': 1, 'is': 10, 'better': 8, 'than': 8, 'ugly': 1, 'explicit': 1, 'implicit': 1, 'simple': 1, 'complex': 2, 'complicated': 1, 'flat': 1, 'nested': 1, 'sparse': 1, 'dense': 1, 'readability': 1, 'counts': 1, 'special': 2, 'cases': 1, \"aren't\": 1, 'enough': 1, 'to': 5, 'break': 1, 'the': 5, 'rules': 1, 'although': 3, 'practicality': 1, 'beats': 1, 'purity': 1, 'errors': 1, 'should': 2, 'never': 3, 'pass': 1, 'silently': 1, 'unless': 2, 'explicitly': 1, 'silenced': 1, 'in': 1, 'face': 1, 'of': 2, 'ambiguity': 1, 'refuse': 1, 'temptation': 1, 'guess': 1, 'there': 1, 'be': 3, 'one': 3, 'and': 1, 'preferably': 1, 'only': 1, 'obvious': 2, 'way': 2, 'do': 2, 'it': 2, 'that': 1, 'may': 2, 'not': 1, 'at': 1, 'first': 1, \"you're\": 1, 'dutch': 1, 'now': 2, 'often': 1, 'right': 1, 'if': 2, 'implementation': 2, 'hard': 1, 'explain': 2, \"it's\": 1, 'a': 2, 'bad': 1, 'idea': 3, 'easy': 1, 'good': 1, 'namespaces': 1, 'are': 1, 'honking': 1, 'great': 1, '': 1, \"let's\": 1, 'more': 1, 'those': 1}\n" + ] + } + ], + "source": [ + "print(zen_map)" + ] + }, + { + "cell_type": "markdown", + "id": "2717da3c", + "metadata": {}, + "source": [ + "У нас хранится отображение из строки в количество раз, в котором мы её встретили. Например, `beautiful` у нас встретилось всего один раз, а `is` встретилось десять раз.\n", + " \n", + "Теперь нам нужно каким-то образом выяснить, какие слова встречаются чаще всего. Обратите внимание, как ключи, так и значения в словаре не упорядочены, поэтому нам нужно сделать так, чтобы они были упорядочены. Для этого можно воспользоваться, например, методом `items` и, например, в `zen_items` мы можем положить `zen_map.items`. Это будет уже список таплов. Обратите внимание, у нас в нашем `zen_items` содержится как ключ, так и значение, но уже в виде тапла, и эту вещь мы уже можем сортировать." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "981664ee", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_items([('beautiful', 1), ('is', 10), ('better', 8), ('than', 8), ('ugly', 1), ('explicit', 1), ('implicit', 1), ('simple', 1), ('complex', 2), ('complicated', 1), ('flat', 1), ('nested', 1), ('sparse', 1), ('dense', 1), ('readability', 1), ('counts', 1), ('special', 2), ('cases', 1), (\"aren't\", 1), ('enough', 1), ('to', 5), ('break', 1), ('the', 5), ('rules', 1), ('although', 3), ('practicality', 1), ('beats', 1), ('purity', 1), ('errors', 1), ('should', 2), ('never', 3), ('pass', 1), ('silently', 1), ('unless', 2), ('explicitly', 1), ('silenced', 1), ('in', 1), ('face', 1), ('of', 2), ('ambiguity', 1), ('refuse', 1), ('temptation', 1), ('guess', 1), ('there', 1), ('be', 3), ('one', 3), ('and', 1), ('preferably', 1), ('only', 1), ('obvious', 2), ('way', 2), ('do', 2), ('it', 2), ('that', 1), ('may', 2), ('not', 1), ('at', 1), ('first', 1), (\"you're\", 1), ('dutch', 1), ('now', 2), ('often', 1), ('right', 1), ('if', 2), ('implementation', 2), ('hard', 1), ('explain', 2), (\"it's\", 1), ('a', 2), ('bad', 1), ('idea', 3), ('easy', 1), ('good', 1), ('namespaces', 1), ('are', 1), ('honking', 1), ('great', 1), ('', 1), (\"let's\", 1), ('more', 1), ('those', 1)])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "zen_items = zen_map.items()\n", + "zen_items" + ] + }, + { + "cell_type": "markdown", + "id": "367d5fc2", + "metadata": {}, + "source": [ + "Как мы будем это делать? Если мы попробуем отсортировать `zen_items` прямо сейчас, то у нас сортировка произойдёт по первому значению, по `beautiful`.\n", + "\n", + "Однако, нам нужно сортировать по второму значению наших таплов, по количеству раз, в котором мы встречали это слово. Для этого нам поможет замечательный модуль `operator` и функция `sorted`. Воспользуемся функцией `sorted` и будем сортировать наши `zen_items`.\n", + "\n", + "Чтобы сортировать по второму значению в нашем тапле, мы можем в качестве аргумента `key` передать туда `operator.itemgetter` и написать единичку, потому что нас интересует именно первый индекс. `beautiful` — это нулевой индекс, единичка — это первый индекс.\n", + "\n", + "Так как нам нужны самые популярные слова, мы используем `reverse=True`." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d3b22620", + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "\n", + "word_count_items = sorted(\n", + " zen_items, key=operator.itemgetter(1), reverse=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "831b3bed", + "metadata": {}, + "source": [ + "Давайте попробуем вывести `word_count_items` и возьмём первые три элемента. Отлично, у нас получилось, что самые популярные слова, это `is`, `better`, `than`, что мы собственно и ожидали." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "80cdc92d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[('is', 10), ('better', 8), ('than', 8)]\n" + ] + } + ], + "source": [ + "print(word_count_items[:3])" + ] + }, + { + "cell_type": "markdown", + "id": "dabaebe1", + "metadata": {}, + "source": [ + "Однако, как это часто бывает в Python'е, есть встроенный модуль, который вам поможет решить эту задачу намного быстрее. Мы можем импортировать из модуля `collections` `Counter`. В случае, если мы используем `Counter`, всё, что нам нужно — это очистить наши слова и передать эти слова в `Counter`.\n", + "\n", + "Давайте именно это и сделаем.\n", + "\n", + "Создадим `cleaned_list`, в который в цикле будем добавлять очищенные слова уже знакомым образом, используя метод `split`, который разбивает строку по пробельным символам, и будем добавлять в `cleaned_lis`t с помощью метода `append` очищенное слово.\n", + "\n", + "Очищенное слово будет получено с помощью метода `strip` и приведения к нижнему регистру с помощью метода `lower`.\n", + "\n", + "Итак, всё, что нам осталось сделать, это вызвать `counter` с `cleaned_list`'ом и обратиться к методу `most_common`, перадать ему троечку.\n", + "\n", + "Давайте попробуем, что у нас получится. Отлично, именно то, что мы ожидали, однако, это достигается с помощью трёх строк кода." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "0c407d54", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[('is', 10), ('better', 8), ('than', 8)]\n" + ] + } + ], + "source": [ + "from collections import Counter\n", + "\n", + "cleaned_list = []\n", + "\n", + "for word in zen.split():\n", + " cleaned_list.append(word.strip(\".,-!\").lower())\n", + " \n", + "print(Counter(cleaned_list).most_common(3))" + ] + }, + { + "cell_type": "markdown", + "id": "89af8ccd", + "metadata": {}, + "source": [ + "Часто в Python'е есть встроенные методы и модули, которые вам позволяют делать какие-то вещи легко и удобно. Отлично, мы с вами познакомились со словарями и решили задачку на их применение." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Словари.ipynb b/2. Структуры данных и функции/1. Коллекции/Словари.ipynb new file mode 100644 index 0000000..f562243 --- /dev/null +++ b/2. Структуры данных и функции/1. Коллекции/Словари.ipynb @@ -0,0 +1,620 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "35e7641e", + "metadata": {}, + "source": [ + "# Словари #" + ] + }, + { + "cell_type": "markdown", + "id": "aa115439", + "metadata": {}, + "source": [ + "На этой лекции мы поговорим про словари.\n", + "\n", + "Словари являются важнейшей структурой данных в Python'е и они позволяют хранить данные в формате ключ-значение.\n", + "\n", + "Чтобы определить словарь, нужно использовать литерал фигурные скобки или просто вызвать `dict`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "647047ab", + "metadata": {}, + "outputs": [], + "source": [ + "empty_dict = {}\n", + "empty_dict = dict()" + ] + }, + { + "cell_type": "markdown", + "id": "b793bf48", + "metadata": {}, + "source": [ + "Если мы хотим определить словарь, в котором уже есть какие-то данные, мы просто пишем наши ключ-значение через двоеточие. Например, словарь collections_map содержит отображение и строк, и списки в листы." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ab12602e", + "metadata": {}, + "outputs": [], + "source": [ + "collections_map = {\n", + " \"mutable\": [\"list\", \"set\", \"dict\"],\n", + " \"immutable\": [\"tuple\", \"frozenset\"]\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "8f0a511f", + "metadata": {}, + "source": [ + "Чем хороши словари? Они позволяют получить значение по ключу за константное время, очень быстро. Это достигается с помощью алгоритма хеширования. Например, мы можем получить `immutable` лист." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ce41b9bf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['tuple', 'frozenset']\n" + ] + } + ], + "source": [ + "print(collections_map[\"immutable\"])" + ] + }, + { + "cell_type": "markdown", + "id": "46286967", + "metadata": {}, + "source": [ + "Если мы попытаемся получить доступ по ключу, которого не существует, у нас Python выдаст нам ошибку KeyError, потому что такого ключа очевидно нет." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "bfbe0151", + "metadata": {}, + "outputs": [ + { + "ename": "KeyError", + "evalue": "'irresistible'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcollections_map\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"irresistible\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mKeyError\u001b[0m: 'irresistible'" + ] + } + ], + "source": [ + "print(collections_map[\"irresistible\"])" + ] + }, + { + "cell_type": "markdown", + "id": "f9f60bb7", + "metadata": {}, + "source": [ + "Однако, часто бывает полезно попробовать взять значение по ключу и в случае неудачи вернуть какое-то дефолтное значение. Для этого есть встроенный метод `get` у словаря, который делает именно это. Например, мы попытались взять ключи `irresistible` и вернули `not found`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "975284c8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "not found\n" + ] + } + ], + "source": [ + "print(collections_map.get(\"irresistible\", \"not found\"))" + ] + }, + { + "cell_type": "markdown", + "id": "ead7601e", + "metadata": {}, + "source": [ + "Чтобы проверить, содержится ли ключ в словаре, мы используем уже знакомые вам операторы." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "faa26fae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"mutable\" in collections_map" + ] + }, + { + "cell_type": "markdown", + "id": "b3a915d7", + "metadata": {}, + "source": [ + "Так как словарь является изменяемой структурой данных, мы можем добавлять и удалять элементы из него. Например, мы можем определить словарь `beatles_map`, который содержит знаменитых музыкантов и их инструменты, и добавить в него Ринго с ударными, просто используя доступ по ключу." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0f6226ae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Paul': 'Bass', 'John': 'Guitar', 'George': 'Guitar'}\n" + ] + } + ], + "source": [ + "beatles_map = {\n", + " \"Paul\": \"Bass\",\n", + " \"John\": \"Guitar\",\n", + " \"George\": \"Guitar\",\n", + "}\n", + "\n", + "print(beatles_map)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0a54daf3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Paul': 'Bass', 'John': 'Guitar', 'George': 'Guitar', 'Ringo': 'Drums'}\n" + ] + } + ], + "source": [ + "beatles_map[\"Ringo\"] = \"Drums\"\n", + "\n", + "print(beatles_map)" + ] + }, + { + "cell_type": "markdown", + "id": "6d010fb3", + "metadata": {}, + "source": [ + "Чтобы удалить ключ и значение из словаря, можно использовать уже знакомый вам оператор del, который удаляет в данном случае Джона из нашего словаря." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "230b9d49", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Paul': 'Bass', 'George': 'Guitar', 'Ringo': 'Drums'}\n" + ] + } + ], + "source": [ + "del beatles_map[\"John\"]\n", + "\n", + "print(beatles_map)" + ] + }, + { + "cell_type": "markdown", + "id": "5d2a6238", + "metadata": {}, + "source": [ + "Также, чтобы добавить какой-то ключ-значение в словарь, можно использовать встроенный метод `update`, который принимает словарь и апдейтит существующий словарь." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d2ca1637", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Paul': 'Bass', 'George': 'Guitar', 'Ringo': 'Drums', 'John': 'Guitar'}\n" + ] + } + ], + "source": [ + "beatles_map.update({\n", + " \"John\": \"Guitar\"\n", + "})\n", + "\n", + "print(beatles_map)" + ] + }, + { + "cell_type": "markdown", + "id": "ffbbe1b1", + "metadata": {}, + "source": [ + "Чтобы удалить ключ-значение из словаря и вернуть значение, можно использовать метод pop, и в данном случае мы удаляем Ринго, и нам возвращаются его ударные." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "c68f6183", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Drums\n", + "{'Paul': 'Bass', 'George': 'Guitar', 'John': 'Guitar'}\n" + ] + } + ], + "source": [ + "print(beatles_map.pop(\"Ringo\"))\n", + "\n", + "print(beatles_map)" + ] + }, + { + "cell_type": "markdown", + "id": "64580aff", + "metadata": {}, + "source": [ + "Часто бывает необходимо не только попробовать проверить, существует ли ключ в словаре, но и в случае неудачи добавить эту новую пару ключ-значение. Для этого есть метод `setdefault`, и давайте посмотрим, как он работает. Мы объявляем `unknown_dict`, который абсолютно пуст, и используя метод `setdefault` пытаемся взять какой-то ключ `key` и в случае неудачи добавим туда `default`." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "403cdacb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "default\n" + ] + } + ], + "source": [ + "unknown_dict = {}\n", + "\n", + "print(unknown_dict.setdefault(\"key\", \"default\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "f8dbee49", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'key': 'default'}" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "unknown_dict" + ] + }, + { + "cell_type": "markdown", + "id": "8fc6bb60", + "metadata": {}, + "source": [ + "Происходит именно это, у нас в нашем пустом словаре получилось отображение `key`, `default` и `default` вернулся к нам в результате вызова метода `setdefault`.\n", + "\n", + "Если мы попытаемся вызвать `setdefault` и в качестве дефолтного значения передаём `new_default`, у нас вернётся значение, которое уже лежит в словаре, значение `default`." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "9b0fa584", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "default\n" + ] + } + ], + "source": [ + "print(unknown_dict.setdefault(\"key\", \"new_default\"))" + ] + }, + { + "cell_type": "markdown", + "id": "3c5cf8c7", + "metadata": {}, + "source": [ + "Словари, как и все коллекции, поддерживают протокол итерации, поэтому мы можем итерироваться по словарю, например, с помощью метода `for`." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "5acefef3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'mutable': ['list', 'set', 'dict'], 'immutable': ['tuple', 'frozenset']}\n" + ] + } + ], + "source": [ + "print(collections_map)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "b4c7bb66", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mutable\n", + "immutable\n" + ] + } + ], + "source": [ + "for key in collections_map:\n", + " print(key)" + ] + }, + { + "cell_type": "markdown", + "id": "74c5246c", + "metadata": {}, + "source": [ + "Обратите внимание, мы итерируемся по ключам словаря. Можем вывести собственно ключи, которые содержатся в нашем словаре. \n", + "\n", + "Если нам нужно итерироваться не по ключам, как по умолчанию, а по ключам и значениям сразу можно использовать метод словаря `items`, который возвращает ключи и значения. Например, можно выводить в нашем `collections_map` как ключи, так и значения." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "342baa0c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mutable: ['list', 'set', 'dict']\n", + "immutable: ['tuple', 'frozenset']\n" + ] + } + ], + "source": [ + "for key, value in collections_map.items():\n", + " print(f\"{key}: {value}\")" + ] + }, + { + "cell_type": "markdown", + "id": "87b8d55f", + "metadata": {}, + "source": [ + "Если нужно итерироваться по значениям, используйте логично метод `values`, который возвращает именно значения. Также существует симметричный метод `keys`, который возвращает оператор ключей." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "56344c1b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['list', 'set', 'dict']\n", + "['tuple', 'frozenset']\n" + ] + } + ], + "source": [ + "for value in collections_map.values():\n", + " print(value)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "3299f648", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mutable\n", + "immutable\n" + ] + } + ], + "source": [ + "for key in collections_map.keys():\n", + " print(key)" + ] + }, + { + "cell_type": "markdown", + "id": "d96b364e", + "metadata": {}, + "source": [ + "Важная особенность словарей в Python'е - они содержат ключи и значения в неупорядоченном виде, то есть вы не можете гарантировать, что ключи, например, отсортированы, или хранятся в том порядке, в каком вы их туда добавили. Однако, в Python'е существует `OrderedDict`, это тип, который содержится в модуле `collections`, который гарантирует вам, что ключи содержатся именно в том порядке, в каком вы их добавили в словарь. Например, мы можем вывести пару, число и строка, именно в том порядке, в каком мы их туда добавили." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "b7ce5f64", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n", + "3\n", + "4\n", + "5\n", + "6\n", + "7\n", + "8\n", + "9\n" + ] + } + ], + "source": [ + "from collections import OrderedDict\n", + "\n", + "ordered = OrderedDict()\n", + "\n", + "for number in range(10):\n", + " ordered[number] = str(number)\n", + " \n", + "for key in ordered:\n", + " print(key)" + ] + }, + { + "cell_type": "markdown", + "id": "8e4e659e", + "metadata": {}, + "source": [ + "По секрету с версии Python 3.8 порядок гарантирован." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "b885b0f1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n", + "3\n", + "4\n", + "5\n", + "6\n", + "7\n", + "8\n", + "9\n" + ] + } + ], + "source": [ + "ordered = dict()\n", + "\n", + "for number in range(10):\n", + " ordered[number] = str(number)\n", + " \n", + "for key in ordered:\n", + " print(key)" + ] + }, + { + "cell_type": "markdown", + "id": "fb29decb", + "metadata": {}, + "source": [ + "Итак, на этой лекции мы обсудили важнейшую структуру данных - словари, которые содержат в неупорядоченном виде набор пар ключ-значение. Вы можете изменять эту структуру данных, потому что она является изменяемой. Словари позволяют вам получать значение по ключу очень быстро за константное время и точно также очень быстро проверять вхождение какого-то ключа в словарь. Это достигается с помощью алгоритмов хеширования. Давайте попробуем решить задачку на словари." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Списки и кортежи.ipynb b/2. Структуры данных и функции/1. Коллекции/Списки и кортежи.ipynb new file mode 100644 index 0000000..088ea37 --- /dev/null +++ b/2. Структуры данных и функции/1. Коллекции/Списки и кортежи.ipynb @@ -0,0 +1,1098 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "164f2285", + "metadata": {}, + "source": [ + "# Списки и кортежи #" + ] + }, + { + "cell_type": "markdown", + "id": "a0860ec6", + "metadata": {}, + "source": [ + "В этой лекции мы поговорим с вами о коллекциях.\n", + "\n", + "Коллекция - это переменная-контейнер, в которой может содержаться какое-то количество объектов, эти объекты могут быть одного типа или разного. В случае списков, о которых далее пойдет речь - это упорядоченные наборы элементов, которые могут быть разных типов. Собственно, списки определяются с помощью квадратных скобочек или с помощью вызова литерала `list`. Вы также можете создать список из одинаковых значений с помощью умножения." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "50d507c5", + "metadata": {}, + "outputs": [], + "source": [ + "empty_list = []\n", + "empty_list = list()\n", + "\n", + "none_list = [None] * 10\n", + "\n", + "collections = [\"list\", \"tuple\", \"dict\", \"set\"]\n", + "\n", + "user_data = [\n", + " [\"Elena\", 4.4],\n", + " [\"Andrey\", 4.2]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "826b2528", + "metadata": {}, + "source": [ + "Чаще всего списки содержат переменные одного типа, например, строки. Однако бывает, что нужно добавлять в списки переменные разных типов. Это тоже возможно. Также списки могут содержать другие коллекции, как, например, `user_data`. Однако для таких данных чаще всего используются кортежи, о которых мы поговорим чуть позже." + ] + }, + { + "cell_type": "markdown", + "id": "1ca1cebf", + "metadata": {}, + "source": [ + "Чтобы получить длину списка, можно вызвать встроенную функцию `len`." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "71a1712b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(collections)" + ] + }, + { + "cell_type": "markdown", + "id": "b8f1100d", + "metadata": {}, + "source": [ + "И, как вы видите, для определения списка не нужно заранее знать длину списка, который вы создаете, списки изменяемые, и Python обо всем заботится о вас. Также интересно, что функция `len` выполняется за константное время, то есть не происходит `full scan`. Python делает все очень быстро." + ] + }, + { + "cell_type": "markdown", + "id": "81b54f7f", + "metadata": {}, + "source": [ + "Чтобы получить доступ к какому-то конкретному элементу списка, мы обращаемся по индексу к нему, также как и в строчках. Например, чтобы получить первый элемент, мы обращаемся по нулевому индексу, так как списки нумеруются с 0, а чтобы последний - к −1." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "4aaa7d90", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['list', 'tuple', 'dict', 'set']\n", + "list\n", + "set\n" + ] + } + ], + "source": [ + "print(collections)\n", + "\n", + "print(collections[0])\n", + "print(collections[-1])" + ] + }, + { + "cell_type": "markdown", + "id": "9314d073", + "metadata": {}, + "source": [ + "Также индексы можно использовать для того, чтобы менять какие-то элементы внутри списка, например, мы можем заменить `set` на `frozenset`, обратившись к переменной по третьему индексу." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "58c77b64", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['list', 'tuple', 'dict', 'frozenset']\n" + ] + } + ], + "source": [ + "collections[3] = \"frozenset\"\n", + "\n", + "print(collections)" + ] + }, + { + "cell_type": "markdown", + "id": "3c710948", + "metadata": {}, + "source": [ + "Если мы обратимся по индексу, которого не существует, Python выдаст вам ошибку, скажет, что `list index out of range`, значит, что такого индекса еще нет. Опять же, ошибки в Python'е очень говорящие." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "0310e5b8", + "metadata": {}, + "outputs": [ + { + "ename": "IndexError", + "evalue": "list index out of range", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mIndexError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mcollections\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mIndexError\u001b[0m: list index out of range" + ] + } + ], + "source": [ + "collections[10]" + ] + }, + { + "cell_type": "markdown", + "id": "e6986171", + "metadata": {}, + "source": [ + "Читайте их, не бойтесь, они вам скажут, что именно произошло не так. Также можно проверить, существует ли конкретный объект в списке, с помощью оператора `in`. Например, у нас есть `tuple` в наших `collections`." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "e7e3adfc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"tuple\" in collections" + ] + }, + { + "cell_type": "markdown", + "id": "026bfc3a", + "metadata": {}, + "source": [ + "Также списки поддерживают срезы так же, как и строки, работает все точно так же. Например, мы можем получить определенный набор элементов с помощью среза от первого до третьего." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "e4cf1f58", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n" + ] + } + ], + "source": [ + "range_list = list(range(10))\n", + "print(range_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "43e860e6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2]" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "range_list[1:3]" + ] + }, + { + "cell_type": "markdown", + "id": "ab45e27a", + "metadata": {}, + "source": [ + "Последний индекс не включается, будьте внимательны, и так как элементы в списках нумеруются с 0, у нас не включается 0 здесь. Мы можем опустить второй индекс и таким образом получить все элементы, начиная с третьего индекса, или, наоборот, до пятого индекса." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "6673dc75", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[3, 4, 5, 6, 7, 8, 9]" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "range_list[3:]" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "66cf8cbb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 1, 2, 3, 4]" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "range_list[:5]" + ] + }, + { + "cell_type": "markdown", + "id": "a107e276", + "metadata": {}, + "source": [ + "Также мы можем опустить оба, и начальный и конечный индекс, и, например, скакать через один, получая только четные элементы списка." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "342bac0e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n" + ] + } + ], + "source": [ + "print(range_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "4b00d12e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 2, 4, 6, 8]" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "range_list[::2]" + ] + }, + { + "cell_type": "markdown", + "id": "02cffa19", + "metadata": {}, + "source": [ + "Мы можем также разворачивать список, обращаясь к −1 или делать более сложные операции, которые довольно редко встречаются, но важно представлять, что именно там происходит." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "c3722991", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "range_list[::-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "ec0f88a5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 3, 5, 7, 9]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "range_list[1::2]" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "26023033", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[5, 4, 3, 2]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "range_list[5:1:-1]" + ] + }, + { + "cell_type": "markdown", + "id": "da4080c4", + "metadata": {}, + "source": [ + "Также важно знать, что при получении среза у вас создается новый объект, то есть получается новый список в этот момент." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "5d0d323f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n" + ] + }, + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(range_list)\n", + "print(range_list[:])\n", + "\n", + "range_list[:] is range_list" + ] + }, + { + "cell_type": "markdown", + "id": "df2e16eb", + "metadata": {}, + "source": [ + "Списки, как и все коллекции, поддерживают протокол итерации, значит, мы можем использовать цикл `for` для того, чтобы итерироваться по элементам списка." + ] + }, + { + "cell_type": "markdown", + "id": "5986024c", + "metadata": {}, + "source": [ + "Обратите внимание, что итерации происходят именно по элементам списка, а не по индексам, как во многих языках.\n", + "\n", + "Например, мы можем итерироваться по нашим collections и выводить текущую коллекцию." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "10d90a2a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Learning list...\n", + "Learning tuple...\n", + "Learning dict...\n", + "Learning set...\n" + ] + } + ], + "source": [ + "collections = [\"list\", \"tuple\", \"dict\", \"set\"]\n", + "\n", + "for collection in collections:\n", + " print(f\"Learning {collection}...\")" + ] + }, + { + "cell_type": "markdown", + "id": "0e1999ee", + "metadata": {}, + "source": [ + "Часто бывает нужно выводить не только элемент списка, а также его индекс. Для этого существует встроенная функция `enumerate`, которая возвращает индекс и текущий элемент.\n", + "\n", + "Например, мы можем вывести `collection` и соответствующий индекс." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "2b746578", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0. list\n", + "1. tuple\n", + "2. dict\n", + "3. set\n" + ] + } + ], + "source": [ + "for idx, collection in enumerate(collections):\n", + " print(f\"{idx}. {collection}\")" + ] + }, + { + "cell_type": "markdown", + "id": "41322654", + "metadata": {}, + "source": [ + "Так как списки являются изменяемыми структурами данных, мы можем добавлять и удалять элементы.\n", + "\n", + "Для добавления элемента в список существует встроенный метод списка `append`, который добавляет переданную переменную в конец списка." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "05b8d29e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['list', 'tuple', 'dict', 'set', 'OrderedDict']\n" + ] + } + ], + "source": [ + "collections.append(\"OrderedDict\")\n", + "\n", + "print(collections)" + ] + }, + { + "cell_type": "markdown", + "id": "f9420aed", + "metadata": {}, + "source": [ + "Например, мы можем добавить в наши `collections` `OrderedDict`.\n", + "\n", + "Если вам нужно расширить список каким-то другим списком, вы можете использовать метод `extend`, который добавляет переданный список в конец вашего списка." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "fbf75b4e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['list', 'tuple', 'dict', 'set', 'OrderedDict', 'ponyset', 'unicorndict']\n" + ] + } + ], + "source": [ + "collections.extend([\"ponyset\", \"unicorndict\"])\n", + "\n", + "print(collections)" + ] + }, + { + "cell_type": "markdown", + "id": "f0683229", + "metadata": {}, + "source": [ + "Также можно использовать перегруженный оператор `+`, который делает логичную вещь, он добавляет переменную в конец, так же как `append` или `extend`." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "bedcf18e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['list', 'tuple', 'dict', 'set', 'OrderedDict', 'ponyset', 'unicorndict', None]\n" + ] + } + ], + "source": [ + "collections += [None]\n", + "\n", + "print(collections)" + ] + }, + { + "cell_type": "markdown", + "id": "2059bc0b", + "metadata": {}, + "source": [ + "Если вам нужно удалить какой-то элемент списка, можно использовать оператор `del` и указать индекс, который вы хотите удалить." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "53e26d94", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['list', 'tuple', 'dict', 'set', 'ponyset', 'unicorndict', None]\n" + ] + } + ], + "source": [ + "del collections[4]\n", + "\n", + "print(collections)" + ] + }, + { + "cell_type": "markdown", + "id": "1b044d2f", + "metadata": {}, + "source": [ + "Также существуют несколько полезных встроенных функций, которые помогут вам работать со списками, это `min`, `max` и `sum`. Они делают именно то, как называются, то есть, мы можем получить минимальный элемент, максимальный элемент или сумму элементов списка." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "eaa5caa3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n", + "19\n", + "80\n" + ] + } + ], + "source": [ + "numbers = [4, 17, 19, 9, 2, 6, 10, 13]\n", + "\n", + "print(min(numbers))\n", + "print(max(numbers))\n", + "print(sum(numbers))" + ] + }, + { + "cell_type": "markdown", + "id": "1e6bf2e7", + "metadata": {}, + "source": [ + "Еще один полезный метод, который работает со списками и строками, это метод строки `join`.\n", + "Он позволяет взять список и форматировать строку с помощью разделителя какого-то." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "6abea50a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "python, perl, pascal\n" + ] + } + ], + "source": [ + "tag_list = [\"python\", \"perl\", \"pascal\"]\n", + "print(\", \".join(tag_list))" + ] + }, + { + "cell_type": "markdown", + "id": "3a8b4d4c", + "metadata": {}, + "source": [ + "Еще одна часто встречающаяся операция со списками - это сортировка. В Python'е существует несколько методов сортировки, о которых мы сейчас с вами поговорим, а для начала создадим случайный список с помощью встроенного модуля `random`. Это модуль стандартной библиотеки, который вам не нужно дополнительно устанавливать. Создадим пустой список и в цикле добавим в него случайный элемент с помощью функции `append`, как вы видите." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "5b839563", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[6, 14, 13, 16, 7, 4, 11, 9, 7, 16]\n" + ] + } + ], + "source": [ + "import random\n", + "\n", + "numbers = []\n", + "\n", + "for _ in range(10):\n", + " numbers.append(random.randint(1, 20))\n", + " \n", + "print(numbers)" + ] + }, + { + "cell_type": "markdown", + "id": "d4f11a29", + "metadata": {}, + "source": [ + "Обратите внимание, что в цикле `for` используется для итерации нижнее подчеркивание, эта переменная говорит о том, что нам не важно, что именно присваивается при итерации.\n", + "Таким образом, нам важно, что происходит итерация 10 раз, 10 раз присваивается новый элемент, а переменная сама нам не важна.\n", + "\n", + "У нас получился какой-то список, давайте попробуем его отсортировать.\n", + "Для сортировки в Python'е существует встроенная функция `sorted`, которая возвращает отсортированный список. Все очень просто и логично." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "0dfdf09a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[4, 6, 7, 7, 9, 11, 13, 14, 16, 16]\n", + "[6, 14, 13, 16, 7, 4, 11, 9, 7, 16]\n" + ] + } + ], + "source": [ + "print(sorted(numbers))\n", + "print(numbers)" + ] + }, + { + "cell_type": "markdown", + "id": "5e8e66b9", + "metadata": {}, + "source": [ + "Обратите внимание, что список возвращается именно новый, то есть старый список сохраняется неизменным. Если вам нужно изменить список, который вы имеете уже, можно использовать метод списка `sort`, который изменяет список `inplace`, сортирует его `inplace`." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "b00c7345", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[4, 6, 7, 7, 9, 11, 13, 14, 16, 16]\n" + ] + } + ], + "source": [ + "numbers.sort()\n", + "print(numbers)" + ] + }, + { + "cell_type": "markdown", + "id": "cd069246", + "metadata": {}, + "source": [ + "Если вам нужно отсортировать список в обратном порядке, можно передать аргумент `reverse=True` в функцию sorted или в функцию sort метод списка." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "e931b1dc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[16, 16, 14, 13, 11, 9, 7, 7, 6, 4]\n" + ] + } + ], + "source": [ + "print(sorted(numbers, reverse=True))" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "97f2d225", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[16, 16, 14, 13, 11, 9, 7, 7, 6, 4]\n" + ] + } + ], + "source": [ + "numbers.sort(reverse=True)\n", + "print(numbers)" + ] + }, + { + "cell_type": "markdown", + "id": "b8dcc547", + "metadata": {}, + "source": [ + "Также существует встроенная функция `reversed`, которая возвращает некий `reverse iterator`." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "d0b2693d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(reversed(numbers))" + ] + }, + { + "cell_type": "markdown", + "id": "6713e790", + "metadata": {}, + "source": [ + "Мы об итераторах поговорим позднее, пока важно понимать, что это просто объект, который поддерживает протокол итерации, и можно его преобразовать в список, а получится, в принципе, то же самое.\n", + "\n", + "Кроме методов, которые мы обсудили в этой лекции, существует также много других, о которых вы можете почитать в документации, например, метод `index` или `insert`." + ] + }, + { + "cell_type": "markdown", + "id": "14e838d3", + "metadata": {}, + "source": [ + "Собственно, в документации все про них подробно написано, не бойтесь это смотреть." + ] + }, + { + "cell_type": "markdown", + "id": "5e5ce574", + "metadata": {}, + "source": [ + "А следующей структурой данных, о которой мы поговорим в этой лекции, являются кортежи.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "b93e3034", + "metadata": {}, + "source": [ + "Кортежи, по сути, это неизменяемые списки, то есть это какой-то упорядоченный набор объектов, который мы не можем изменить - мы не можем ни добавлять, ни удалять элементы из него. \n", + "\n", + "Кортежи определяются с помощью круглых скобочек или литерала `tuple`." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "1caefad6", + "metadata": {}, + "outputs": [], + "source": [ + "empty_tuple = ()\n", + "empty_tuple = tuple()" + ] + }, + { + "cell_type": "markdown", + "id": "8e935365", + "metadata": {}, + "source": [ + "Например, мы можем создать кортеж `immutables`, куда положим неизменяемые типы наши." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "b431f577", + "metadata": {}, + "outputs": [], + "source": [ + "immutables = (int, str, tuple)" + ] + }, + { + "cell_type": "markdown", + "id": "de831021", + "metadata": {}, + "source": [ + "Если мы попробуем в качестве нулевого элемента сделать `float`, у нас выведется ошибка, потому что кортежи неизменяемы." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "3f059a2f", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'tuple' object does not support item assignment", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mimmutables\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mfloat\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m: 'tuple' object does not support item assignment" + ] + } + ], + "source": [ + "immutables[0] = float" + ] + }, + { + "cell_type": "markdown", + "id": "b0b7177b", + "metadata": {}, + "source": [ + "Однако будьте внимательны, несмотря на то что сами кортежи неизменяемые, объекты внутри них могут быть изменяемыми.\n", + "\n", + "Например, если у нас кортеж содержит список, мы можем добавлять элементы в этот список. Например, мы можем присоединить нолик." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "7c5acc0a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "([0], [])\n" + ] + } + ], + "source": [ + "blink = ([], [])\n", + "blink[0].append(0)\n", + "\n", + "print(blink)" + ] + }, + { + "cell_type": "markdown", + "id": "f5f5d867", + "metadata": {}, + "source": [ + "Также важная особенность кортежей — у них есть функция `hash`, к ним применяется функция `hash`, и поэтому они могут использоваться в качестве ключей в словарях, о которых мы поговорим чуть позднее." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "1e0e50e1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "750394483" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hash(tuple())" + ] + }, + { + "cell_type": "markdown", + "id": "eaaab34b", + "metadata": {}, + "source": [ + "Будьте внимательней при определении кортежа из одного элемента, не забывайте писать запятую, потому что если вы забудете про нее, то Python сочтет вашу переменную типом `int`." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "ebb0c4e9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "int" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "one_element_tuple = (1,)\n", + "guess_what = (1)\n", + "\n", + "type(guess_what)" + ] + }, + { + "cell_type": "markdown", + "id": "1a57f366", + "metadata": {}, + "source": [ + "Итак, на этой лекции мы с вами поговорили про списки.\n", + "\n", + "Списки — это упорядоченный и изменяемый набор объектов. Они поддерживают итерацию, вы можете применять к ним срезы или обращаться к элементам по индексам. Также в них довольно много встроенных методов и каких-то функций, которые вы можете использовать вместе со списками.\n", + "\n", + "Также мы разобрали кортежи.\n", + "Кортежи — это неизменяемая версия списков, и кортежи могут использоваться в качестве ключей в словарях.\n", + "\n", + "На следующей лекции мы посмотрим с вами на задачу на изученный материал." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Списки. Пример программы.ipynb b/2. Структуры данных и функции/1. Коллекции/Списки. Пример программы.ipynb new file mode 100644 index 0000000..9063225 --- /dev/null +++ b/2. Структуры данных и функции/1. Коллекции/Списки. Пример программы.ipynb @@ -0,0 +1,315 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3c0c7af7", + "metadata": {}, + "source": [ + "# Списки. Пример программы #" + ] + }, + { + "cell_type": "markdown", + "id": "7cd71aeb", + "metadata": {}, + "source": [ + "Итак, мы с вами разобрали, как работают списки. Давайте попробуем решить задачу на их применение.\n", + "\n", + "В качестве примера попробуем найти медиану случайного списка." + ] + }, + { + "cell_type": "markdown", + "id": "1384a6a8", + "metadata": {}, + "source": [ + "Медиана - это значение в отсортированном списке, которое лежит ровно посередине, таким образом, половина значений - слева от него, и половина значений - справа." + ] + }, + { + "cell_type": "markdown", + "id": "50a2cb56", + "metadata": {}, + "source": [ + "Чтобы получить медиану случайного списка, нам нужно случайный список создать." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8139fe20", + "metadata": {}, + "outputs": [], + "source": [ + "import random" + ] + }, + { + "cell_type": "markdown", + "id": "84628ca0", + "metadata": {}, + "source": [ + "Давайте воспользуемся для этого модулем `random` в стандартной библиотеке.\n", + "\n", + "Заведём наш список `numbers`. Давайте, у нас будет какое-то случайное количество элементов в этом списке, чтобы было интереснее.\n", + "\n", + "У нас будет `number_size`, который мы получим с помощью функции `randint`. Функция `randint` возвращает какое-то случайное значение в интервале, ей переданном.\n", + "\n", + "Таким образом, у нас `number_size` будет равняться от 10 до 15, какому-то числу." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "29199966", + "metadata": {}, + "outputs": [], + "source": [ + "numbers = []\n", + "numbers_size = random.randint(10, 15)" + ] + }, + { + "cell_type": "markdown", + "id": "c0c7e008", + "metadata": {}, + "source": [ + "Теперь нам нужно создать наш случайный список. Давайте воспользуемся циклом `for` и встроенной функцией `range`. Будем интерироваться ровно `number_size` раз. \n", + "\n", + "Обратите внимание, я использую переменную нижнее подчёркивание, которая говорит о том, что нам не интересно, в принципе, что в неё записывается. Мы не будем её использовать. Нам важно, чтобы итерация происходила ровно `number_size` раз.\n", + "\n", + "И будем добавлять в наши `numbers` с помощью знакомого вам метода `append` какое-то новое число. А случайное число мы будем получать с помощью той же самой функции `randint`, и пусть это число будет от 10 до 20." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "95d1de3b", + "metadata": {}, + "outputs": [], + "source": [ + "for _ in range(numbers_size):\n", + " numbers.append(random.randint(10, 20))" + ] + }, + { + "cell_type": "markdown", + "id": "1cd2eb85", + "metadata": {}, + "source": [ + "Давайте выведем наш список. Отлично." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "26e888a2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[15, 15, 16, 17, 10, 18, 13, 20, 20, 17]\n" + ] + } + ], + "source": [ + "print(numbers)" + ] + }, + { + "cell_type": "markdown", + "id": "328d7ffd", + "metadata": {}, + "source": [ + "У нас получился список со случайными значениями, они не отсортированы, и, как видно, от 11 до 20, насколько я вижу.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "701ba286", + "metadata": {}, + "source": [ + "Теперь, чтобы найти медиану, нам нужно список отсортировать. Давайте используем для этого встроенный в список метод `sort` и отсортируем его." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c96230f4", + "metadata": {}, + "outputs": [], + "source": [ + "numbers.sort()" + ] + }, + { + "cell_type": "markdown", + "id": "b7e71ccc", + "metadata": {}, + "source": [ + "`sort` сортирует `in place`, поэтому наш список уже должен быть отсортирован, давайте выведем его." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5dc5b158", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[10, 13, 15, 15, 16, 17, 17, 18, 20, 20]\n" + ] + } + ], + "source": [ + "print(numbers)" + ] + }, + { + "cell_type": "markdown", + "id": "f3a7b55e", + "metadata": {}, + "source": [ + "Отлично, 10 в начале, 20 в конце, значит, список отсортирован.\n", + "\n", + "Теперь нам нужно взять какое-то среднее значение. Как вы могли догадаться, случая может быть два. Если у нас количество элементов в списке нечётное, то всё просто. Мы просто берём средний элемент. Если количество элементов чётное, то по определению медианы нам нужно взять среднее арифметическое от двух средних элементов.\n", + "\n", + "Давайте заведем переменную `half_size`, в которую положим значение, равное половине длины списка. Для этого используем встроенную функцию `len` и поделим нашу длину пополам. И заведём переменную медиана, которая будет для начала равняться `None`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "49d11c5e", + "metadata": {}, + "outputs": [], + "source": [ + "half_size = len(numbers) // 2\n", + "median = None" + ] + }, + { + "cell_type": "markdown", + "id": "2b920f46", + "metadata": {}, + "source": [ + "Итак, в простейшем случае у нас нечётное количество элементов. Если у нас наш `number_size` нечётный, то есть при делении на два даёт остаток один, то мы просто берём в качестве медианы `numbers[half_size]`.\n", + " \n", + "Однако интересное происходит, когда у нас чётное количество элементов в нашем списке. В таком случае нам нужно взять среднее из двух чисел, которые лежат посередине. Воспользуемся знакомой вам функцией `sum` встроенной и срезом. Нам нужен срез от `half_size -1` до `half_size +1`.\n", + "\n", + "Обратите внимание, элементы нумеруются с нуля, поэтому именно так нам нужно поступать. И мы берём среднее арифметическое, поэтому делим на два." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "460fd334", + "metadata": {}, + "outputs": [], + "source": [ + "if numbers_size % 2 == 1:\n", + " median = numbers[half_size]\n", + "else:\n", + " median = sum(\n", + " numbers[half_size - 1:half_size + 1]\n", + " ) / 2" + ] + }, + { + "cell_type": "markdown", + "id": "18bd5ff2", + "metadata": {}, + "source": [ + "Давайте попробуем вывести нашу медиану." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "8fd0d3ae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "16.5\n" + ] + } + ], + "source": [ + "print(median)" + ] + }, + { + "cell_type": "markdown", + "id": "33912a15", + "metadata": {}, + "source": [ + "У нас получилось значение, и, на самом деле, это действительно медиана нашего списка.\n", + "\n", + "Чтобы это проверить, можно воспользоваться встроенным модулем `statistics`, который позволяет нам сделать то, что мы делали только что какое-то заметное количество времени, намного быстрее.\n", + "\n", + "Давайте импортируем модуль `statistics`, и у `statistics` есть метод `median`, который позволит нам найти медиану очень легко. Отлично." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "71dd213f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "16.5" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import statistics\n", + "\n", + "statistics.median(numbers)" + ] + }, + { + "cell_type": "markdown", + "id": "13664bbb", + "metadata": {}, + "source": [ + "Медиана действительно равна 16.5. Мы с вами не только разобрали списки, но и посмотрели задачу на их применение." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Тест по коллекциям (Clear).ipynb b/2. Структуры данных и функции/1. Коллекции/Тест по коллекциям (Clear).ipynb new file mode 100644 index 0000000..3f7a81d --- /dev/null +++ b/2. Структуры данных и функции/1. Коллекции/Тест по коллекциям (Clear).ipynb @@ -0,0 +1,106 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4244c488", + "metadata": {}, + "source": [ + "# Тест по коллекциям #" + ] + }, + { + "cell_type": "markdown", + "id": "7345eb54", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "302b4c7d", + "metadata": {}, + "source": [ + "##### 1. Выберите верные утверждения про списки:\n", + "\n", + "- [ ] списки могут содержать элементы различных типов\n", + "- [ ] списки изменяемые\n", + "- [ ] проверка на вхождение элемента в список происходит за линейное время\n", + "- [ ] списки неизменяемые\n", + "- [ ] проверка на вхождение элемента в список происходит за константное время" + ] + }, + { + "cell_type": "markdown", + "id": "1c801377", + "metadata": {}, + "source": [ + "##### 2. К чему приведет обращение к непустому списку по индексу \"-1\"?\n", + "\n", + "- [ ] Ошибка `IndexError`\n", + "- [ ] Ошибка `KeyError`\n", + "- [ ] Вернется первый элемент\n", + "- [ ] Вернется последний элемент" + ] + }, + { + "cell_type": "markdown", + "id": "80eb70ce", + "metadata": {}, + "source": [ + "##### 3. Выберите верные утверждения про словари:\n", + "\n", + "- [ ] поиск ключа в словаре происходит за константное время\n", + "- [ ] поиск ключа в словаре происходит за линейное время\n", + "- [ ] словари изменяемые\n", + "- [ ] словари неизменяемые" + ] + }, + { + "cell_type": "markdown", + "id": "0143258b", + "metadata": {}, + "source": [ + "##### 4. Можно ли изменять список, находящийся внутри кортежа?\n", + "\n", + "- [ ] Да\n", + "- [ ] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "cace4fb3", + "metadata": {}, + "source": [ + "##### 5. В чем отличие стандартного метода списка `sort` и встроенное функции `sorted`?\n", + "\n", + "- [ ] `Sorted` сортирует исходный список, а `sort` возвращает новый\n", + "- [ ] `Sort` сортирует исходный список, а `sorted` возвращает новый\n", + "- [ ] Отличий нет\n", + "- [ ] Функции `sorted` не существует" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Тест по коллекциям.ipynb b/2. Структуры данных и функции/1. Коллекции/Тест по коллекциям.ipynb new file mode 100644 index 0000000..aa0e111 --- /dev/null +++ b/2. Структуры данных и функции/1. Коллекции/Тест по коллекциям.ipynb @@ -0,0 +1,97 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4244c488", + "metadata": {}, + "source": [ + "# Тест по коллекциям #" + ] + }, + { + "cell_type": "markdown", + "id": "302b4c7d", + "metadata": {}, + "source": [ + "1. Выберите верные утверждения про списки:\n", + "\n", + "- [x] списки могут содержать элементы различных типов\n", + "- [x] списки изменяемые\n", + "- [x] проверка на вхождение элемента в список происходит за линейное время\n", + "- [ ] списки неизменяемые\n", + "- [ ] проверка на вхождение элемента в список происходит за константное время" + ] + }, + { + "cell_type": "markdown", + "id": "1c801377", + "metadata": {}, + "source": [ + "2. К чему приведет обращение к непустому списку по индексу \"-1\"?\n", + "\n", + "- [ ] Ошибка `IndexError`\n", + "- [ ] Ошибка `KeyError`\n", + "- [ ] Вернется первый элемент\n", + "- [x] Вернется последний элемент" + ] + }, + { + "cell_type": "markdown", + "id": "80eb70ce", + "metadata": {}, + "source": [ + "3. Выберите верные утверждения про словари:\n", + "\n", + "- [x] поиск ключа в словаре происходит за константное время\n", + "- [ ] поиск ключа в словаре происходит за линейное время\n", + "- [x] словари изменяемые\n", + "- [ ] словари неизменяемые" + ] + }, + { + "cell_type": "markdown", + "id": "0143258b", + "metadata": {}, + "source": [ + "4. Можно ли изменять список, находящийся внутри кортежа?\n", + "\n", + "- [x] Да\n", + "- [ ] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "cace4fb3", + "metadata": {}, + "source": [ + "5. В чем отличие стандартного метода списка `sort` и встроенное функции `sorted`?\n", + "\n", + "- [ ] `Sorted` сортирует исходный список, а `sort` возвращает новый\n", + "- [x] `Sort` сортирует исходный список, а `sorted` возвращает новый\n", + "- [ ] Отличий нет\n", + "- [ ] Функции `sorted` не существует" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/1. Коллекции/Тест по коллекциям.pdf b/2. Структуры данных и функции/1. Коллекции/Тест по коллекциям.pdf new file mode 100644 index 0000000..248002b Binary files /dev/null and b/2. Структуры данных и функции/1. Коллекции/Тест по коллекциям.pdf differ diff --git a/2. Структуры данных и функции/2. Функции/Aннотация типов.ipynb b/2. Структуры данных и функции/2. Функции/Aннотация типов.ipynb new file mode 100644 index 0000000..d94b7c9 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Aннотация типов.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6da43522", + "metadata": {}, + "source": [ + "# Aннотация типов #" + ] + }, + { + "cell_type": "markdown", + "id": "a086d5d9", + "metadata": {}, + "source": [ + "В Python'е последних версий появилась возможность аннотировать типы, и делается это с помощью двоеточия в случае параметров, и вот такой вот стрелочкой, если мы хотим указать, какого типа возвращаемое значение должно вернуться из функции." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2ae84c77", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "21\n", + "One, two\n" + ] + } + ], + "source": [ + "def add(x: int, y: int) -> int:\n", + " s: int = x + y\n", + " return s\n", + "\n", + "print(add(10, 11))\n", + "print(add(\"One, \", \"two\"))" + ] + }, + { + "cell_type": "markdown", + "id": "80d0226a", + "metadata": {}, + "source": [ + "Однако, что интересно, если мы передадим даже параметры других типов, как в данном случае, то у нас код все равно исполняется, потому что Python - это динамический язык, и аннотация типов призвана помочь программисту или его `IDE` отловить какие-то ошибки.\n", + "\n", + "Тем не менее код все равно исполняется. Если вы считаете это необходимым, можете использовать аннотацию типов, а так же изучить пакет `typing` и `mypy`.\n", + "\n", + "Рекомендую еще использовать аннотацию типов в кавычках, что может Вас огородить от ряда проблем с цеклическими импортами." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9c6913fa", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import TYPE_CHECKING\n", + "\n", + "if TYPE_CHECKING:\n", + " from typing import List\n", + "\n", + "def to_list(x: \"int\", y: \"int\") -> \"List[int]\":\n", + " return [x, y]" + ] + }, + { + "cell_type": "markdown", + "id": "1cc049a2", + "metadata": {}, + "source": [ + "Aннотацию типов можно производить с помощью комментариев." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d9133920", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import TYPE_CHECKING\n", + "\n", + "if TYPE_CHECKING:\n", + " from typing import List\n", + "\n", + "def to_list(x, y):\n", + " # type: (int, int) -> List[int]\n", + " \n", + " result = [x, y] # type: List[int]\n", + " \n", + " return result" + ] + }, + { + "cell_type": "markdown", + "id": "a01ed930", + "metadata": {}, + "source": [ + "Aннотацию типов можно производить в отдельном файле с расширением `.pyi`\n", + "\n", + "```python\n", + "from typing import List\n", + "\n", + "def to_list(x: \"int\", y: \"int\") -> \"List[int]\": ...\n", + "```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/filename b/2. Структуры данных и функции/2. Функции/filename new file mode 100644 index 0000000..6412173 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/filename @@ -0,0 +1,2 @@ +The world is changed. +I taste it in the water. diff --git a/2. Структуры данных и функции/2. Функции/log.txt b/2. Структуры данных и функции/2. Функции/log.txt new file mode 100644 index 0000000..b5045cc --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/log.txt @@ -0,0 +1 @@ +21 \ No newline at end of file diff --git a/2. Структуры данных и функции/2. Функции/new_log.txt b/2. Структуры данных и функции/2. Функции/new_log.txt new file mode 100644 index 0000000..b5045cc --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/new_log.txt @@ -0,0 +1 @@ +21 \ No newline at end of file diff --git a/2. Структуры данных и функции/2. Функции/storage.py b/2. Структуры данных и функции/2. Функции/storage.py new file mode 100644 index 0000000..41875f0 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/storage.py @@ -0,0 +1,73 @@ +"""Реализация Key-value хранилища""" + +from argparse import ArgumentParser +from json import dumps, loads +from os import path, remove +from tempfile import gettempdir +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Dict, List, Optional + +STORAGE_PATH: "str" = path.join(gettempdir(), "storage.data") + + +def get_data() -> "Dict[str,List[str]]": + """Получение всех данных из хранилища""" + + result: "Dict[str,List[str]]" = {} + + if path.exists(STORAGE_PATH): + with open(STORAGE_PATH, "r", encoding="utf-8") as des: + raw_data: "str" = des.read() + + if raw_data: + result = loads(raw_data) + + return result + + +def put(key: "str", value: "str") -> "None": + """Запись значения по ключу""" + + data: "Dict[str,List[str]]" = get_data() + + if key in data: + data[key].append(value) + + else: + data[key] = [ + value, + ] + + with open(STORAGE_PATH, "w", encoding="utf-8") as des: + des.write(dumps(data)) + + +def get(key) -> "Optional[str]": + """Получение данных по ключу""" + + data: "Dict[str,List[str]]" = get_data() + + return None if not key in data else ", ".join(data.get(key)) + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("--key", help="Key") + parser.add_argument("--val", help="Value") + parser.add_argument("--clear", action="store_true", help="Clear") + + args = parser.parse_args() + + if args.clear: + remove(STORAGE_PATH) + + elif args.key and args.val: + put(args.key, args.val) + + elif args.key: + print(get(args.key)) + + else: + print("Wrong command") diff --git a/2. Структуры данных и функции/2. Функции/to_json.py b/2. Структуры данных и функции/2. Функции/to_json.py new file mode 100644 index 0000000..53af39a --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/to_json.py @@ -0,0 +1,14 @@ +"""Реализация декоратора to_json""" + +from functools import wraps +from json import dumps + + +def to_json(func): + """Декоратора to_json""" + + @wraps(func) + def wrapped(*args, **kwargs): + return dumps(func(*args, **kwargs)) + + return wrapped diff --git a/2. Структуры данных и функции/2. Функции/Генераторы.ipynb b/2. Структуры данных и функции/2. Функции/Генераторы.ipynb new file mode 100644 index 0000000..1a9e0f3 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Генераторы.ipynb @@ -0,0 +1,562 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "748ad676", + "metadata": {}, + "source": [ + "# Генераторы #" + ] + }, + { + "cell_type": "markdown", + "id": "81ac59b4", + "metadata": {}, + "source": [ + "На этой лекции мы с вами обсудим генераторы, которые являются очень важным механизмом, на котором построено многое в Python'е.\n", + "\n", + "Итак, простейший генератор — это функция, в которой есть оператор `yield`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "20b39e22", + "metadata": {}, + "outputs": [], + "source": [ + "def even_range(start, end):\n", + " current = start\n", + " while current < end:\n", + " yield current\n", + " current += 2" + ] + }, + { + "cell_type": "markdown", + "id": "8dd3b8b1", + "metadata": {}, + "source": [ + "Что же делает наш генератор? У нас генератор — это `even_range`, который работает по упрощенной схеме знакомого вам генератора `range`. Он принимает `(start, end)`, записывает текущий `start`, и пока у нас `current` меньше конечного значения, он `yield`'ит `current` и прибавляет двойку." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c4a176d4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "2\n", + "4\n", + "6\n", + "8\n" + ] + } + ], + "source": [ + "for number in even_range(0, 10):\n", + " print(number)" + ] + }, + { + "cell_type": "markdown", + "id": "94a8da7f", + "metadata": {}, + "source": [ + "Что же делает этот оператор `yield`? `Yield` можно рассматривать как какой-то временный `return`, то есть у нас возвращается значение `current`. Однако выполнение функции не прекращается, это не обычный `return`, `return`, как вы видите, здесь вот в конце, у нас `return None` по умолчанию. `Yield` возвращает значение, но прерывает выполнение функции только на время, то есть мы можем вернуться к этой функции, к этому моменту.\n", + "\n", + "Важно знать, что мы можем итерироваться, например, по генератору, можем пробежаться и вывести все значения." + ] + }, + { + "cell_type": "markdown", + "id": "f2e7ddcd", + "metadata": {}, + "source": [ + "Каждый раз, когда у нас выполняется `yield`, у нас возвращается значение `current`, и каждый раз, когда мы просим следующий элемент, у нас выполнение функции возвращается в этот же момент, и мы идем дальше. Чтобы посмотреть, как это происходит на самом деле, можно воспользоваться функцией next, которая действительно применяется каждый раз при итерации. Таким образом, мы создаем наш итератор, наш генератор, и вызываем `next`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f652e038", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ranger = even_range(0, 4)\n", + "next(ranger)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "78b9cbcb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "next(ranger)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "30534428", + "metadata": {}, + "outputs": [ + { + "ename": "StopIteration", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mStopIteration\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mranger\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mStopIteration\u001b[0m: " + ] + } + ], + "source": [ + "next(ranger)" + ] + }, + { + "cell_type": "markdown", + "id": "5d8c18d1", + "metadata": {}, + "source": [ + "В начале у нас получается 0. Потом вызывается `next`, получается 2. И когда у нас наш генератор исчерпан, когда у нас условие, что текущее значение меньше 4 не выполняется, — у нас вызывается `StopIteration`, выбрасывается исключение и больше `yield`'ов нет, у нас происходит `return`.\n", + "\n", + "Именно так работают генераторы. Чтобы понять, что действительно происходит, можно, например, добавить сюда `print` после `yield`'а." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c7a44318", + "metadata": {}, + "outputs": [], + "source": [ + "def list_generator(list_obj):\n", + " for item in list_obj:\n", + " yield item\n", + " print(f\"After yielding {item}\")\n", + " \n", + "generator = list_generator([1, 2])" + ] + }, + { + "cell_type": "markdown", + "id": "3ee7ee21", + "metadata": {}, + "source": [ + "Давайте посмотрим, что происходит при вызове `next`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "441b6575", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "next(generator)" + ] + }, + { + "cell_type": "markdown", + "id": "b405a97b", + "metadata": {}, + "source": [ + "У нас есть наш `list_generator`, он принимает `list` какой-то `object` и просто `yield`'ит все элементы по порядку. Мы вызываем `next(generator)`, то есть берем следующий элемент из генератора, у нас возвращается единичка, первый элемент списка. Логично. Однако у нас почему-то не вывелось `print`. Не вывелось именно потому, что у нас выполнение функции генератора прервано. На этом моменте у нас вернулся элемент, и у нас запомнилось состояние функции в какой-то этот момент. " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "870d8e66", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "After yielding 1\n" + ] + }, + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "next(generator)" + ] + }, + { + "cell_type": "markdown", + "id": "64be79b0", + "metadata": {}, + "source": [ + "И выводится `After yielding` у нас выводится, когда мы берем следующий элемент, то есть у нас выполнение продолжается с вот этого момента и идет до тех пор, пока нет следующего `yield`'а. Таким образом мы доходим до двойки и все." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "420efed7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "After yielding 2\n" + ] + }, + { + "ename": "StopIteration", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mStopIteration\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mgenerator\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mStopIteration\u001b[0m: " + ] + } + ], + "source": [ + "next(generator)" + ] + }, + { + "cell_type": "markdown", + "id": "cab954f3", + "metadata": {}, + "source": [ + "Собственно, чем полезны эти генераторы? Казалось бы, концепт понятный, но не понятно, зачем их использовать. Оказывается, очень важной бывает именно вот эта вот особенность, которая позволяет хранить состояние функции и возвращаться к этому состоянию раз за разом. Таким образом, например, реализована функция `range`. Функция `range`, как вы знаете, позволяет вам получить генератор, какой-то итерабельный объект, по которому мы можем пробежаться и, например, выводить просто в цикле числа или делать что-нибудь более сложное. Функция `range` позволяет вам не загружать в память сразу огромный список чисел. Таким образом раньше в Python'е, например, была функция `range` безгенераторная и `xrange` генераторная. Функция `range` загружала в память огромное количество чисел, чтобы потом по ним итерироваться. Генератор позволяет вам делать очень просто. Он позволяет запомнить текущее положение и от него уже идти дальше." + ] + }, + { + "cell_type": "markdown", + "id": "f534d54f", + "metadata": {}, + "source": [ + "Классический пример — это числа Фибоначчи." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "ff283572", + "metadata": {}, + "outputs": [], + "source": [ + "def fibonacci(number):\n", + " a = b = 1\n", + " \n", + " for _ in range(number):\n", + " yield a\n", + " a, b = b, a + b" + ] + }, + { + "cell_type": "markdown", + "id": "d4edb25a", + "metadata": {}, + "source": [ + "Нам нужно получить числа Фибоначчи до какого-то момента. Опять же, обратите внимание, нам не нужно запоминать огромное количество чисел Фибоначчи, которые очень быстро растут. Нам нужно здесь только помнить два конкретных числа и возвращаться в момент, когда мы остановились." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ca0e7f6b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "1\n", + "2\n", + "3\n", + "5\n", + "8\n", + "13\n", + "21\n", + "34\n", + "55\n" + ] + } + ], + "source": [ + "for num in fibonacci(10):\n", + " print(num)" + ] + }, + { + "cell_type": "markdown", + "id": "8af9fb69", + "metadata": {}, + "source": [ + "Итак, очень важная особенность генераторов, что они позволяют хранить состояние и возвращаться к нему и использовать это для того, чтобы оптимизировать работу с памятью. " + ] + }, + { + "cell_type": "markdown", + "id": "c9054bfd", + "metadata": {}, + "source": [ + "Еще один очень важный момент — это возможность генераторам получать какие-то значения. Мы можем не только возвращаться к моменту, когда у нас генератор сделал `yield`, и продолжать исполнение с этого момента с каким-то контекстом, мы можем еще и передавать туда значения. Этот момент используется очень активно в асинхронном программировании, о котором мы с вами поговорим чуть позже." + ] + }, + { + "cell_type": "markdown", + "id": "f33f1bd3", + "metadata": {}, + "source": [ + "А пока давайте определим `accumulator`, нашу генераторную функцию, которая хранит какое-то общее количество данных и просто в бесконечном цикле получает с помощью оператора `yield` значение. Давайте посмотрим, что здесь происходит в простейшем виде." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "97d62f6d", + "metadata": {}, + "outputs": [], + "source": [ + "def accumulator():\n", + " total = 0\n", + " while True:\n", + " value = yield total\n", + " print(f\"Got: {value}\")\n", + " \n", + " if not value: break\n", + " \n", + " total += value\n", + " \n", + "generator = accumulator()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "161da2c5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "next(generator)" + ] + }, + { + "cell_type": "markdown", + "id": "55ec02a9", + "metadata": {}, + "source": [ + "Вначале мы инициализируем наш генератор, то есть мы его стартуем. У нас возвращается `yield total`, которая равна нулю по умолчанию в начале цикла. Дальше, мы можем послать данные в генератор с помощью метода генератора `send`." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "e359097d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Got: 1\n", + "Accumulated: 1\n" + ] + } + ], + "source": [ + "print(f\"Accumulated: {generator.send(1)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "c6c8b6c3", + "metadata": {}, + "source": [ + "Что же здесь мы делаем? Мы передаем в наш генератор единичку, мы посылаем туда значение, то есть у нас есть какая-то точка исполнения, у нас остановилось исполнение здесь. Мы можем послать в эту точку значение, и это значение запишется в `value`. Таким образом мы передали в `value` единицу. Мы можем вывести, что мы действительно получили единицу.\n", + "\n", + "Дальше, если у нас в `value` не было передано, мы хотим выйти. У нас есть `value`, поэтому мы прибавляем к нашему `total`'у `value`, которому мы передали. Собственно, accumulator аккумулирует значение, что логично. Итак, у нас накопилась единичка.\n", + "\n", + "Дальше, мы можем послать еще одну единичку и окажется, что мы действительно получили единичку, а накопили уже два, потому что наша функция хранит состояние, и у нас хранится контекст, в котором у нас `total` уже равен двум." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "9d27bdf5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Got: 1\n", + "Accumulated: 2\n" + ] + } + ], + "source": [ + "print(f\"Accumulated: {generator.send(1)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "0b4adf6b", + "metadata": {}, + "source": [ + "Мы можем передать еще одну единичку, и у нас очевидно `Accumulated` станет равен трем." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "8972b01d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Got: 1\n", + "Accumulated: 3\n" + ] + } + ], + "source": [ + "print(f\"Accumulated: {generator.send(1)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "2e9d9826", + "metadata": {}, + "source": [ + "Мы передали единичку, о чем и говорим. Если мы ничего не передадим, а просто пойдем дальше, то у нас по условию выхода из цикла, как вы помните, заканчивается наш генератор и выбрасывается исключение `StopIteration`." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "13dace76", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Got: None\n" + ] + }, + { + "ename": "StopIteration", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mStopIteration\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mgenerator\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mStopIteration\u001b[0m: " + ] + } + ], + "source": [ + "next(generator)" + ] + }, + { + "cell_type": "markdown", + "id": "6e6b2588", + "metadata": {}, + "source": [ + "Видите, мы ничего не получили, мы получили `None`, поэтому мы закончили выполнение.\n", + " \n", + "Итак, генераторы являются очень важной концепцией, которая используется во многих особенностях языка Python, например, в асинхронном программировании. Они позволяют вам создавать функции, к которым можно возвращаться, которые хранят контекст выполнения и позволяют вам оптимально работать с памятью." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Декораторы.ipynb b/2. Структуры данных и функции/2. Функции/Декораторы.ipynb new file mode 100644 index 0000000..b206aaa --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Декораторы.ipynb @@ -0,0 +1,787 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "13c4cdb4", + "metadata": {}, + "source": [ + "# Декораторы #" + ] + }, + { + "cell_type": "markdown", + "id": "2fd8abca", + "metadata": {}, + "source": [ + "На этой лекции мы с вами поговорим о декораторах, и это очень важная концепция, которую действительно нужно понять, потому что они используются повсеместно практически во всех приложениях на Python'е. \n", + "\n", + "Итак, декоратор - это функция, которая принимает функцию и возвращает функцию. Это очень важный момент, пожалуйста, запомните это." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "053894bf", + "metadata": {}, + "outputs": [], + "source": [ + "def decorator(func):\n", + " return func" + ] + }, + { + "cell_type": "markdown", + "id": "7818e41d", + "metadata": {}, + "source": [ + "Просто функция, принимающая одну функцию, возвращающая другую функцию. Как вы уже знаете, в Python'е функция - это объект первого класса, поэтому их можно возвращать, принимать и производить с ними разные операции.\n", + "\n", + "Итак, простейший декоратор просто возвращает функцию и возвращает её же, то есть такой `identity` декоратор, который ничего не делает. Однако, часто бывает полезно с функцией что-то делать, и мы с вами это сейчас как раз обсудим." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9c451b1e", + "metadata": {}, + "outputs": [], + "source": [ + "@decorator\n", + "def decorated():\n", + " print(\"Hello!\")" + ] + }, + { + "cell_type": "markdown", + "id": "497c0bb8", + "metadata": {}, + "source": [ + "Синтаксис применения декоратора в Python'е такой. На самом деле, это просто так называемый синтаксический сахар, и происходит здесь именно это. У нас вызывается декоратор, ему передаётся функция и записывается всё в функцию, которую мы декорируем." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0c2285bb", + "metadata": {}, + "outputs": [], + "source": [ + "decorated = decorator(decorated)" + ] + }, + { + "cell_type": "markdown", + "id": "19dc0fd2", + "metadata": {}, + "source": [ + "Делается это обычно как раз с помощью `@`. Не нужно этого бояться, происходит здесь довольно простой момент вот такой. \n", + "\n", + "Итак, однако, как вы понимаете, довольно скучно просто возвращать саму же функцию, и нет в этом никакого смысла. Часто бывает необходимо вернуть не ту же самую функцию, а какую-то модифицированную функцию или вообще новую функцию совершенно другую.\n", + "\n", + "Например, ещё один простой декоратор, мы можем его определить, он принимает функцию, определяет внутри какую-то новую функцию и возвращает её. Таким образом, у нас при применении декоратора, если мы вызовем `decorated`, не вызовется `hello`, потому что вернётся, на самом деле, функция `new_func`, и мы будем вызывать уже функцию `new_func`. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "21fa4573", + "metadata": {}, + "outputs": [], + "source": [ + "def decorator(func):\n", + " def new_func():\n", + " pass\n", + " \n", + " return new_func\n", + "\n", + "@decorator\n", + "def decorated():\n", + " print(\"Hello!\")\n", + "\n", + "decorated()" + ] + }, + { + "cell_type": "markdown", + "id": "303da690", + "metadata": {}, + "source": [ + "Можем это проверить, если посмотрим на её имя. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "96066a0b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "new_func\n" + ] + } + ], + "source": [ + "print(decorated.__name__)" + ] + }, + { + "cell_type": "markdown", + "id": "293001dc", + "metadata": {}, + "source": [ + "На самом деле, `decorated` — это `new_func`, потому что опять же у нас вот такой вот синтаксис. У нас декоратор вернул другую функцию, и в `decorated` записалась новая функция `new_func`." + ] + }, + { + "cell_type": "markdown", + "id": "aa2fd676", + "metadata": {}, + "source": [ + "Важно это понимать. Итак, давайте попробуем сразу перейти к практике, чтобы поподробнее разобраться с тем, как это работает. \n", + "\n", + "И напишем декоратор, который записывает в лог результаты выполнения функции. Вообще для чего обычно используются декораторы? Чаще всего они используются для того, чтобы модифицировать поведение каких-то функций. Часто бывает необходимо использовать один декоратор, для того чтобы какое-то семейство функций переопределить, модифицировать их поведение. \n", + "\n", + "Например, у нас может быть декоратор `login_required`, который мы можем применять к разным функциям и требовать, чтобы в момент исполнения этой функции человек был залогинен на сайте. Или мы можем опять же логировать какую-то информацию, можем считать какие-то метрики, и написав один декоратор, мы можем модифицировать поведение сразу многих функций. Это очень полезный и мощный концепт, который, собственно, мы с вами сейчас рассмотрим.\n", + "\n", + "Итак, у нас декоратор, который просто пишет что-то, что произошло в функции, в файл. Мы вот здесь вот определили наш декоратор, назовём его `logger`, и вот так вот он будет применяться. И теперь при вызове `summator`'а мы хотим, чтобы у нас результат выполнения `summator`'а записывался в лог-файл. Также мы можем этот декоратор применять к любой другой функции. У нас всегда результат выполнения будет записываться в файл вне зависимости от того, какая это функция." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "24f785fd", + "metadata": {}, + "outputs": [], + "source": [ + "def logger(func):\n", + " ...\n", + "\n", + "@logger\n", + "def summator(num_list):\n", + " return sum(num_list)\n", + "\n", + "# summator([1, 2, 3, 4])" + ] + }, + { + "cell_type": "markdown", + "id": "7411ba1d", + "metadata": {}, + "source": [ + "Давайте попробуем это сделать. Итак, мы определили наш декоратор, и нам нужно из него очевидно вернуть какую-то новую функцию. Давайте назовём ее `wrapped`. Обычно функции, которые определены внутри декоратора, называются `wrapped`, `decorated`, `inner`, что-то в этом роде, чтобы понять, что это действительно то новое, чем мы переопределяем изначальную функцию.\n", + "\n", + "И у нас функция будут принимать `num_list` для начала, также как и наш `summator`, потому что мы заменим наш `summator` на этот `wrapped(num_list)`. Мы потом вернём его, перезаписав `summator`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "33aba2ae", + "metadata": {}, + "outputs": [], + "source": [ + "def logger(func):\n", + " def wrapped(num_list):\n", + " pass\n", + " \n", + " return wrapped" + ] + }, + { + "cell_type": "markdown", + "id": "71c217c5", + "metadata": {}, + "source": [ + "Что же здесь должно происходить? Воспользуемся знакомым вашим концептом замыкания и вызовем нашу функцию, передав ей `num_list`, и, например, откроем файл и запишем в него результат выполнения. Не забудем вернуть `result`, потому что мы хотим, чтобы у нас, на самом деле, всё работало." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "596da402", + "metadata": {}, + "outputs": [], + "source": [ + "def logger(func):\n", + " def wrapped(num_list):\n", + " result = func(num_list)\n", + " \n", + " with open(\"log.txt\", \"w\") as f:\n", + " f.write(str(result))\n", + " \n", + " return result\n", + " \n", + " return wrapped" + ] + }, + { + "cell_type": "markdown", + "id": "05bae755", + "metadata": {}, + "source": [ + "Итак, мы написали наш декоратор. Что здесь происходит? Мы, применяя декоратор, подменяем функцию `summator` новой функцией `wrapped`, и именно она уже будет выполняться. Эта функция новая, она принимает `num_list`, так же как и `summator`, получает результат работы `summator`'а, записывает результат в файл и просто возвращается. Давайте попробуем вызвать summator." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "d9bb65eb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "15" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "@logger\n", + "def summator(num_list):\n", + " return sum(num_list)\n", + "\n", + "summator([1, 2, 3, 4, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "4a881941", + "metadata": {}, + "source": [ + "У нас вернулось 15, потому что сумма, действительно, 15. Давайте попробуем открыть файл log.txt и убедиться, что там действительно что-то записано." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d10ea350", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "15\n" + ] + } + ], + "source": [ + "with open(\"log.txt\", \"r\") as f:\n", + " print(f.read())" + ] + }, + { + "cell_type": "markdown", + "id": "73ca8968", + "metadata": {}, + "source": [ + "Да, у нас действительно в `log.txt` записано 15. Отлично. У нас появился декоратор, который мы можем применять совершенно к разным функциям, которые принимают `num_list`, и они записывают результаты выполнения в файл.\n", + "\n", + "Так, однако, часто бывает полезно определить декоратор так, чтобы он мог применяться не только к функциям, которые принимают `num_list`, а, например, к функциям, которые принимают любое количество аргументов, любое количество параметров. Как вы могли догадаться, нам нужно нашу функцию определить так, чтобы она принимала любое количество аргументов. Мы делаем это с помощью звёздочек и передаём все аргументы, которые нам сюда пришли в нашу исходную функцию. Всё должно работать точно так же." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "b2bed53b", + "metadata": {}, + "outputs": [], + "source": [ + "def logger(func):\n", + " def wrapped(*args, **kwargs):\n", + " result = func(*args, **kwargs)\n", + " \n", + " with open(\"log.txt\", \"w\") as f:\n", + " f.write(str(result))\n", + " \n", + " return result\n", + " \n", + " return wrapped" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "046731ea", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "@logger\n", + "def summator(num_list):\n", + " return sum(num_list)\n", + "\n", + "summator([1, 2, 3, 4])" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "4b3feec2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10\n" + ] + } + ], + "source": [ + "with open(\"log.txt\", \"r\") as f:\n", + " print(f.read())" + ] + }, + { + "cell_type": "markdown", + "id": "fbfe35dd", + "metadata": {}, + "source": [ + "Да, действительно, если мы вызовем `summator` и выведем результат выполнения summator'а, у нас `summator` получил 10 и в файле записалось 10. Всё работает." + ] + }, + { + "cell_type": "markdown", + "id": "874a38cb", + "metadata": {}, + "source": [ + "Однако, есть один важный момент, и чтобы понять, чем он неудачен, можно написать, попробовать получить имя `summator`'а. Оказывается, `summator` у нас — это `wrapped`." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "9360cc5f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "wrapped\n" + ] + } + ], + "source": [ + "print(summator.__name__)" + ] + }, + { + "cell_type": "markdown", + "id": "66eff2ab", + "metadata": {}, + "source": [ + "Как вы могли помнить, у нас функция подменяется, поэтому, на самом деле, `summator` больше не `summator`. Часто это бывает неудобно, потому что для каких-то методов интроспекции, для отладки важно помнить, в какой функции именно, например, произошло исключение. Для этого существует встроенный модуль `functools` с методом `wraps`, который вам позволяет немного сделать поприятнее, и он подменяет определённые аргументы, `docstring`'и и названия, для того чтобы у нас `summator` остался `summator`'ом, и у нас имя, видите, стало вновь `summator`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "88f873c9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Summator: 21\n", + "summator\n" + ] + } + ], + "source": [ + "import functools\n", + "\n", + "def logger(func):\n", + " @functools.wraps(func)\n", + " def wrapped(*args, **kwargs):\n", + " result = func(*args, **kwargs)\n", + " \n", + " with open(\"log.txt\", \"w\") as f:\n", + " f.write(str(result))\n", + " return result\n", + " \n", + " return wrapped\n", + "\n", + "\n", + "@logger\n", + "def summator(num_list):\n", + " return sum(num_list)\n", + "\n", + "print(f\"Summator: {summator([1, 2, 3, 4, 5, 6])}\")\n", + "print(summator.__name__)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "010a833e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "21\n" + ] + } + ], + "source": [ + "with open(\"log.txt\", \"r\") as f:\n", + " print(f.read())" + ] + }, + { + "cell_type": "markdown", + "id": "be0170f8", + "metadata": {}, + "source": [ + "Собственно, с этим логгером у нас всё. У нас появился декоратор, который может декорировать функции и записывать их результат в файл.\n", + "\n", + "Давайте, пойдём дальше и напишем более сложный логгер. Так, наша задача будет написать декоратор с параметром, который записывает лог-файл, то есть делает то же самое, однако может принимать в качестве параметра файл, в который нужно записать.\n", + "\n", + "Что же для этого нам нужно сделать? \n", + "\n", + "Давайте попробуем подумать. Есть наш новый декоратор, который принимает уже не функцию, а принимает `filename`. Собственно, записывает в этот `filename` результат выполнения функции.\n", + "\n", + "Что должен вернуть наш декоратор? Он должен вернуть декоратор, то есть, на самом деле, у нас можно рассматривать `logger` не как декоратор, а как просто функцию, которая возвращает декоратор. И возвращает она декоратор, который принимает функцию. Таким образом, мы вызовем `logger` вначале, у нас вернётся декоратор, и потом этот декоратор уже будет применяться к функции `summator`." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "e0bf0f15", + "metadata": {}, + "outputs": [], + "source": [ + "def logger(filename):\n", + " def decorator(func):\n", + " pass\n", + " \n", + " return decorator\n", + "\n", + "@logger(\"new_log.txt\")\n", + "def summator(num_list):\n", + " return sum(num_list)\n", + "\n", + "# summator([1, 2, 3, 4, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "c9c12801", + "metadata": {}, + "source": [ + "Что у нас происходит внутри декоратора? Всё абсолютно то же самое, у нас есть wrapped или decorated, мы можем назвать, который принимает какое-то количество аргументов, нам не важно, какое. Собственно, наш декоратор потом её возвращает." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "e436af25", + "metadata": {}, + "outputs": [], + "source": [ + "def logger(filename):\n", + " def decorator(func):\n", + " def wrapped(*args, **kwargs):\n", + " pass\n", + " \n", + " return wrapped\n", + " \n", + " return decorator\n", + "\n", + "@logger(\"new_log.txt\")\n", + "def summator(num_list):\n", + " return sum(num_list)\n", + "\n", + "# summator([1, 2, 3, 4, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "241d2f1f", + "metadata": {}, + "source": [ + "Отлично. Что же происходит внутри? У нас выполняется наша функция, записывается всё в `result`, в функцию мы передаём аргументы её. И, обратите внимание, когда мы открываем файл, мы уже используем не `log.txt`, а `filename` с верхнего уровня с знакомым вам замыканием, и записываем в `filename` результаты выполнения функции. Да, и не забудем, конечно, вернуть `result`, чтобы всё работало." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "7c793e93", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "21" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def logger(filename):\n", + " def decorator(func):\n", + " def wrapped(*args, **kwargs):\n", + " result = func(*args, **kwargs)\n", + " \n", + " with open(filename, \"w\") as f:\n", + " f.write(str(result))\n", + " \n", + " return result\n", + " \n", + " return wrapped\n", + " \n", + " return decorator\n", + "\n", + "@logger(\"new_log.txt\")\n", + "def summator(num_list):\n", + " return sum(num_list)\n", + "\n", + "summator([1, 2, 3, 4, 5, 6])" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "622ec4a2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "21\n" + ] + } + ], + "source": [ + "with open(\"new_log.txt\", \"r\") as f:\n", + " print(f.read())" + ] + }, + { + "cell_type": "markdown", + "id": "39ff16bd", + "metadata": {}, + "source": [ + "Так, давайте попробуем запустить. У нас 21, и откроем `new_log.txt` и проверим, сколько там записалось. Да, у нас в `new_log.txt` 21, а значит произошло ровно то, что мы хотели. Давайте посмотрим более внимательно на этот пример. Что здесь происходит? У нас применяется декоратор к функции `summator`. Вызывается функция `logger`, которой мы передаём `filename`. Функция `logger` возвращает декоратор, который потом уже с помощью `@` применяется по знакомому вам правилу. Этот декоратор принимает функцию, в данном случае это `summator`, и возвращает новую функцию `wrapped`, которая подменяет наш `summator`, и потом вызывается именно она. И внутри этой функции вызывается исходная функция, то есть `summator`, и записывается результат её выполнения в файл. Ничего сложного, и можно написать аналог. Для синтаксического сахара это выглядит как-то так." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "b2f9d4ab", + "metadata": {}, + "outputs": [], + "source": [ + "summator = logger(\"log.txt\")(summator)" + ] + }, + { + "cell_type": "markdown", + "id": "e7a339c4", + "metadata": {}, + "source": [ + "У нас есть `logger`, который принимает `log.txt`, возвращает декоратор, который принимает summator и возвращает новую функцию. На самом деле, происходит что-то в этом роде.\n", + "\n", + "Итак, мы с вами рассмотрели, пожалуй, самый сложный пример на декораторы, и часто бывают именно с такими примерами проблемы. Разобраться в том, какой декоратор что принимает, а что возвращает, довольно сложно. Пожалуйста, обратите на это внимание. Это очень важный концепт, который правда очень часто используется." + ] + }, + { + "cell_type": "markdown", + "id": "a3a7cea3", + "metadata": {}, + "source": [ + "И последний пример на декораторы. Давайте посмотрим, что будет, если применить сразу несколько декораторов. Декораторы можно чейнить, то есть создавать цепочки из декораторов, определяя сразу несколько декораторов один друг за другом. Например, нам нужно модифицировать как-то поведение функции в каких-то нескольких плоскостях, например, мы можем проверять, залогинен ли пользователь и переданы ли какие-то данные, необходимые для того, чтобы происходила дальше транзакция какая-то или операция. \n", + "\n", + "Определим два простых декоратора, первый декоратор и второй декоратор, который просто принтит и вызывает функцию." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "11336dfb", + "metadata": {}, + "outputs": [], + "source": [ + "def first_decorator(func):\n", + " def wrapped():\n", + " print(\"Inside first_decorator product\")\n", + " return func()\n", + " \n", + " return wrapped\n", + "\n", + "def second_decorator(func):\n", + " def wrapped():\n", + " print(\"Inside second_decorator product\")\n", + " return func()\n", + " \n", + " return wrapped" + ] + }, + { + "cell_type": "markdown", + "id": "58cdcbc2", + "metadata": {}, + "source": [ + "Применим наши декораторы к функции `decorated` и посмотрим, в каком порядке они вызовутся." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "e4d147dd", + "metadata": {}, + "outputs": [], + "source": [ + "@first_decorator\n", + "@second_decorator\n", + "def decorated():\n", + " print(\"Finally called...\")" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "1a98d952", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Inside first_decorator product\n", + "Inside second_decorator product\n", + "Finally called...\n" + ] + } + ], + "source": [ + "decorated()" + ] + }, + { + "cell_type": "markdown", + "id": "0861caa2", + "metadata": {}, + "source": [ + "У нас вначале вызвался первый декоратор, потом — второй декоратор. Почему так произошло? Чтобы понять почему, можно написать опять же в простом виде без синтаксического сахара и разобраться, что у нас происходит при применении декораторов." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "a05eacb4", + "metadata": {}, + "outputs": [], + "source": [ + "decorated = first_decorator(second_decorator(decorated))" + ] + }, + { + "cell_type": "markdown", + "id": "6542fd87", + "metadata": {}, + "source": [ + "Вначале у нас вызывается функция `second_decorator`, которая возвращает новую функцию `wrapped`, таким образом, у нас в целом подменяется функция `wrap` внутри `second_dercorator`'а. После этого вызывается `first_decorator`, который принимает функцию полученную из `second_decorator`'а `wrapped` и возвращает ещё одну функцию `wrapped`, заменяя `decorated` на неё. Таким образом, у нас итоговая функция `decorated` — это вот эта вот функция, вызывающая функцию из `second_decorator`'а. Когда у нас вызывается `decorated`, у нас вначале пишется `print`, потом вызывается вот эта вот функция, которая на самом деле попала сюда из `second_decorator`'а, и потом принтится `second_decorator`. \n", + "\n", + "Будьте с этим внимательны. Порядок применения декораторов очень важен, и от него часто зависит поведение.\n", + "\n", + "И ещё один пример на чейн декораторов немного другой. Если мы опишем два декоратора `bold` и `italic` и попробуем, например, отформатировать текст, окажется, что у нас `italic` внутри `bold`'а, хотя, казалось бы, у нас только что декоратор выполнялся сверху вниз, а здесь - снизу вверх." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "72d0d6d5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello world\n" + ] + } + ], + "source": [ + "def bold(func):\n", + " def wrapped():\n", + " return \"\" + func() + \"\"\n", + " \n", + " return wrapped\n", + "\n", + "\n", + "def italic(func):\n", + " def wrapped():\n", + " return \"\" + func() + \"\"\n", + " \n", + " return wrapped\n", + "\n", + "@bold\n", + "@italic\n", + "def hello():\n", + " return \"hello world\"\n", + "\n", + "# hello = bold(italic(hello))\n", + "print(hello())" + ] + }, + { + "cell_type": "markdown", + "id": "bab7df7e", + "metadata": {}, + "source": [ + "То же самое, чтобы разобраться, что здесь происходит, нужно просто записать это в классическом виде и посмотреть. У нас вначале вызывается функция `italic`, которая возвращает `wrapped`, потом вызывается `bold`, и именно вот функция `wrapped` из `bold`'а записывается в `hello`. Она и будет вызываться. У нас вызывается `hello`, вызывается функция `wrapped`, которая внутри себя возвращает вот такой вот объект. \n", + "\n", + "Что происходит? Чтобы создать этот объект, нам нужно вызвать функцию вот эту вот. А функция `func` в данном случае, на самом деле, эта функция из `italic`'а. И именно поэтому у нас вначале внутри написан тег `i`, а не `b`, как вы могли подумать, читая сверху вниз.\n", + "\n", + "Опять же будьте внимательны в порядке применения декораторов, это очень важно. Итак, на этом всё. Мы с вами разобрали всё, что хотели, научились применять декораторы, создавать свои новые декораторы, делать декораторы с параметрами, и попробовали декораторы чейнить, то есть применять их один за другим. Пожалуйста, разберитесь с этим концептом, это очень важно." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Документация.ipynb b/2. Структуры данных и функции/2. Функции/Документация.ipynb new file mode 100644 index 0000000..2bf9e3d --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Документация.ipynb @@ -0,0 +1,47 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "bf5fb96a", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "73f4d123", + "metadata": {}, + "source": [ + "- [Туториал по функциям из документации](https://docs.python.org/3/tutorial/controlflow.html#defining-functions \"4. More Control Flow Tools\")\n", + "- [Работа с файлами](https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files \"7. Input and Output\")\n", + "- [Встроенные функции](https://docs.python.org/3/library/functions.html \"2. Built-in Functions\")\n", + "- [Сортировка](https://docs.python.org/3/howto/sorting.html \"Sorting HOW TO\")\n", + "- [Функциональное программирование](https://docs.python.org/3/howto/functional.html \"Functional Programming HOWTO\")\n", + "- [Модуль functools](https://docs.python.org/3/library/functools.html \"10.2. functools — Higher-order functions and operations on callable objects\")\n", + "- [Декораторы](https://www.codeschool.com/blog/2016/05/12/a-guide-to-python-decorators/ \"A Guide to Python Decorators\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Задание. Key-value хранилище.ipynb b/2. Структуры данных и функции/2. Функции/Задание. Key-value хранилище.ipynb new file mode 100644 index 0000000..de71ac0 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Задание. Key-value хранилище.ipynb @@ -0,0 +1,200 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4a1dd7c8", + "metadata": {}, + "source": [ + "# Задание. Key-value хранилище #" + ] + }, + { + "cell_type": "markdown", + "id": "4ba56dc0", + "metadata": {}, + "source": [ + "В этом блоке мы с вами реализуем собственный `key-value storage`. Вашей задачей будет написать скрипт, который принимает в качестве аргументов ключи и значения и выводит информацию из хранилища (в нашем случае — из файла)." + ] + }, + { + "cell_type": "markdown", + "id": "2cdd1fd1", + "metadata": {}, + "source": [ + "Запись значения по ключу" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f72ba88b", + "metadata": {}, + "outputs": [], + "source": [ + "! python storage.py --key key1 --val value1" + ] + }, + { + "cell_type": "markdown", + "id": "3ecc5e82", + "metadata": {}, + "source": [ + "Получение значения по ключу.\n", + "\n", + "Ответом в данном случае будет вывод с помощью print соответствующего значения:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4d251cca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "value1\r\n" + ] + } + ], + "source": [ + "! python storage.py --key key1" + ] + }, + { + "cell_type": "markdown", + "id": "91c57474", + "metadata": {}, + "source": [ + "или" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8346149f", + "metadata": {}, + "outputs": [], + "source": [ + "! python storage.py --key key1 --val value2" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c6e52029", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "value1, value2\r\n" + ] + } + ], + "source": [ + "! python storage.py --key key1" + ] + }, + { + "cell_type": "markdown", + "id": "e1e413c0", + "metadata": {}, + "source": [ + "Если значений по этому ключу было записано несколько. Метрики сохраняйте в порядке их добавления. Обратите внимание на пробел после запятой." + ] + }, + { + "cell_type": "markdown", + "id": "c16d0e67", + "metadata": {}, + "source": [ + "Если значений по ключу не было найдено, выводите пустую строку или `None`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "520ff264", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "None\r\n" + ] + } + ], + "source": [ + "! python storage.py --key key2" + ] + }, + { + "cell_type": "markdown", + "id": "0646920d", + "metadata": {}, + "source": [ + "Для работы с аргументами командной строки используйте модуль [argparse](https://docs.python.org/3/howto/argparse.html \"Argparse Tutorial\"). Вашей задачей будет считать аргументы, переданные вашей программе, и записать соответствующую пару ключ-значение в файл хранилища или вывести значения, если был передан только ключ. Хранить данные вы можете в формате **JSON** с помощью стандартного модуля [json](https://docs.python.org/3/library/json.html \"19.2. json — JSON encoder and decoder\"). Проверьте добавление нескольких ключей и разных значений.\n", + "\n", + "Файл следует создавать с помощью модуля [tempfile](https://docs.python.org/3/library/tempfile.html \"11.6. tempfile — Generate temporary files and directories\")." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0fbd81cf", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import tempfile\n", + "\n", + "storage_path = os.path.join(tempfile.gettempdir(), \"storage.data\")\n", + "with open(storage_path, \"w\") as f:\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "id": "de5a5212", + "metadata": {}, + "source": [ + "Создайте скрипт хранилища." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "2a4a9396", + "metadata": {}, + "outputs": [], + "source": [ + "! python storage.py --clear" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Задание. Декоратор to_json.ipynb b/2. Структуры данных и функции/2. Функции/Задание. Декоратор to_json.ipynb new file mode 100644 index 0000000..279bc52 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Задание. Декоратор to_json.ipynb @@ -0,0 +1,105 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b656cccd", + "metadata": {}, + "source": [ + "# Задание. Декоратор to_json #" + ] + }, + { + "cell_type": "markdown", + "id": "4f319165", + "metadata": {}, + "source": [ + "Чтобы передавать данные между функциями, модулями или разными системами используются форматы данных. Одним из самых популярных форматов является `JSON`. Напишите декоратор `to_json`, который можно применить к различным функциям, чтобы преобразовывать их возвращаемое значение в `JSON`-формат. Не забудьте про сохранение корректного имени декорируемой функции.\n", + "\n", + "```python\n", + "@to_json\n", + "def get_data():\n", + " return {\n", + " \"data\": 42\n", + " }\n", + " \n", + "get_data() # вернёт '{\"data\": 42}'\n", + "```\n", + "\n", + "Опишите декоратор в файле." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "41266401", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'{\"data\": 42}'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import json\n", + "\n", + "json.dumps({\"data\": 42})" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "993cc205", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'{\"data\": 42}'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from to_json import to_json\n", + "\n", + "@to_json\n", + "def get_data():\n", + " return {\n", + " \"data\": 42\n", + " }\n", + "\n", + "get_data()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Тест по блоку (Clear).ipynb b/2. Структуры данных и функции/2. Функции/Тест по блоку (Clear).ipynb new file mode 100644 index 0000000..a4f7229 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Тест по блоку (Clear).ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "88beaeb4", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "7aafcb76", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "a30f04bc", + "metadata": {}, + "source": [ + "##### 1. Можно ли использовать изменяемые объекты в качестве значений по умолчанию в функциях?\n", + "\n", + "- [ ] Нет, случится синтаксическая ошибка\n", + "- [ ] Да, но это может привести к неочевидным ошибкам" + ] + }, + { + "cell_type": "markdown", + "id": "791c8018", + "metadata": {}, + "source": [ + "##### 2. Выберите верные утверждения про кортежи:\n", + "\n", + "- [ ] кортежи изменяемые\n", + "- [ ] кортежи могут содержать элементы различных типов\n", + "- [ ] проверка на вхождение элемента в кортеж происходит за константное время\n", + "- [ ] проверка на вхождение элемента в кортеж происходит за линейное время\n", + "- [ ] кортежи неизменяемые" + ] + }, + { + "cell_type": "markdown", + "id": "69434533", + "metadata": {}, + "source": [ + "##### 3. Какой записи эквивалентно применение декоратора?\n", + "\n", + "```python\n", + "@login_required\n", + "def send_feedback(request)\n", + "```\n", + "\n", + "- [ ] `send_feedback = login_required(send_feedback)`\n", + "- [ ] `def login_required(send_feedback)`\n", + "- [ ] `login_required = send_feedback(login_required)`\n", + "- [ ] `def login_required(send_feedback)(request)`" + ] + }, + { + "cell_type": "markdown", + "id": "b11c0bb1", + "metadata": {}, + "source": [ + "##### 4. Для чего используются декораторы?\n", + "\n", + "- [ ] Чтобы иметь возможность импортировать функцию в другой модуль\n", + "- [ ] Для эффективного использования памяти при итерации\n", + "- [ ] Для модификации поведения функций" + ] + }, + { + "cell_type": "markdown", + "id": "acae95b5", + "metadata": {}, + "source": [ + "##### 5. Выберите верные утверждения про множества:\n", + "\n", + "- [ ] проверка на вхождение элемента в множество происходит за константное время\n", + "- [ ] множества изменяемые\n", + "- [ ] проверка на вхождение элемента в множество происходит за линейное время\n", + "- [ ] множества неизменяемые" + ] + }, + { + "cell_type": "markdown", + "id": "7a359014", + "metadata": {}, + "source": [ + "##### 6. Что происходит при итерации по генератору?\n", + "\n", + "- [ ] Каждую итерацию вызывается функция `next`, и генератор исполняется с начала\n", + "- [ ] Итерация происходит по списку значений, который вернул генератор при вызове\n", + "- [ ] Каждую итерацию вызывается функция `next`, и исполнение генератора возобновляется с момента после `yield`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Тест по блоку.ipynb b/2. Структуры данных и функции/2. Функции/Тест по блоку.ipynb new file mode 100644 index 0000000..78a010a --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Тест по блоку.ipynb @@ -0,0 +1,113 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "88beaeb4", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "178d0d2a", + "metadata": {}, + "source": [ + "1. Можно ли использовать изменяемые объекты в качестве значений по умолчанию в функциях?\n", + "\n", + "- [ ] Нет, случится синтаксическая ошибка\n", + "- [x] Да, но это может привести к неочевидным ошибкам" + ] + }, + { + "cell_type": "markdown", + "id": "824d16df", + "metadata": {}, + "source": [ + "2. Выберите верные утверждения про кортежи:\n", + "\n", + "- [ ] кортежи изменяемые\n", + "- [x] кортежи могут содержать элементы различных типов\n", + "- [ ] проверка на вхождение элемента в кортеж происходит за константное время\n", + "- [x] проверка на вхождение элемента в кортеж происходит за линейное время\n", + "- [x] кортежи неизменяемые" + ] + }, + { + "cell_type": "markdown", + "id": "db457384", + "metadata": {}, + "source": [ + "3. Какой записи эквивалентно применение декоратора?\n", + "\n", + "```python\n", + "@login_required\n", + "def send_feedback(request)\n", + "```\n", + "\n", + "- [x] `send_feedback = login_required(send_feedback)`\n", + "- [ ] `def login_required(send_feedback)`\n", + "- [ ] `login_required = send_feedback(login_required)`\n", + "- [ ] `def login_required(send_feedback)(request)`" + ] + }, + { + "cell_type": "markdown", + "id": "3fd4a5b6", + "metadata": {}, + "source": [ + "4. Для чего используются декораторы?\n", + "\n", + "- [ ] Чтобы иметь возможность импортировать функцию в другой модуль\n", + "- [ ] Для эффективного использования памяти при итерации\n", + "- [x] Для модификации поведения функций" + ] + }, + { + "cell_type": "markdown", + "id": "659228ff", + "metadata": {}, + "source": [ + "5. Выберите верные утверждения про множества:\n", + "\n", + "- [x] проверка на вхождение элемента в множество происходит за константное время\n", + "- [x] множества изменяемые\n", + "- [ ] проверка на вхождение элемента в множество происходит за линейное время\n", + "- [ ] множества неизменяемые" + ] + }, + { + "cell_type": "markdown", + "id": "bb5b6db9", + "metadata": {}, + "source": [ + "6. Что происходит при итерации по генератору?\n", + "\n", + "- [ ] Каждую итерацию вызывается функция `next`, и генератор исполняется с начала\n", + "- [ ] Итерация происходит по списку значений, который вернул генератор при вызове\n", + "- [x] Каждую итерацию вызывается функция `next`, и исполнение генератора возобновляется с момента после `yield`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Тест по блоку.pdf b/2. Структуры данных и функции/2. Функции/Тест по блоку.pdf new file mode 100644 index 0000000..14ad153 Binary files /dev/null and b/2. Структуры данных и функции/2. Функции/Тест по блоку.pdf differ diff --git a/2. Структуры данных и функции/2. Функции/Тест по функциям (Clear).ipynb b/2. Структуры данных и функции/2. Функции/Тест по функциям (Clear).ipynb new file mode 100644 index 0000000..f241a4a --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Тест по функциям (Clear).ipynb @@ -0,0 +1,98 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d37f7d4c", + "metadata": {}, + "source": [ + "# Тест по функциям #" + ] + }, + { + "cell_type": "markdown", + "id": "d10fac6b", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "c81785e7", + "metadata": {}, + "source": [ + "##### 1. Что по умолчанию возвращает функция, где не определен `return`?\n", + "\n", + "- [ ] 0\n", + "- [ ] В каждой функции необходимо использовать оператор `return`\n", + "- [ ] `None`\n", + "- [ ] 1" + ] + }, + { + "cell_type": "markdown", + "id": "1301ae95", + "metadata": {}, + "source": [ + "##### 2. Как оформляется тело функции в **Python**?\n", + "\n", + "- [ ] Операторами `BEGIN-END`\n", + "- [ ] Отступом\n", + "- [ ] Фигурными скобками\n", + "- [ ] Квадратными скобками" + ] + }, + { + "cell_type": "markdown", + "id": "a52fd238", + "metadata": {}, + "source": [ + "##### 3. Что произойдет при вызове функции `foo`?\n", + "\n", + "```python\n", + "def foo(*args, **kwargs): pass\n", + "```\n", + "\n", + "- [ ] Все именованные аргументы запишутся в кортеж `kwargs`\n", + "- [ ] Все именованные аргументы запишутся в словарь `kwargs`\n", + "- [ ] Синтаксическая ошибка\n", + "- [ ] Все позиционные аргументы запишутся в кортеж `args`\n" + ] + }, + { + "cell_type": "markdown", + "id": "2384b8b4", + "metadata": {}, + "source": [ + "##### 4. В каком случае можно вызвать функцию без параметров?\n", + "\n", + "- [ ] Если используются `*args`, `**kwargs`\n", + "- [ ] У каждой функции должны быть параметры, в этом суть функций\n", + "- [ ] Если у всех аргументов есть значения по умолчанию\n", + "- [ ] Если она не ожидает аргументов" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Тест по функциям.ipynb b/2. Структуры данных и функции/2. Функции/Тест по функциям.ipynb new file mode 100644 index 0000000..4568a81 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Тест по функциям.ipynb @@ -0,0 +1,89 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d37f7d4c", + "metadata": {}, + "source": [ + "# Тест по функциям #" + ] + }, + { + "cell_type": "markdown", + "id": "c81785e7", + "metadata": {}, + "source": [ + "1. Что по умолчанию возвращает функция, где не определен `return`?\n", + "\n", + "- [ ] 0\n", + "- [ ] В каждой функции необходимо использовать оператор `return`\n", + "- [x] `None`\n", + "- [ ] 1" + ] + }, + { + "cell_type": "markdown", + "id": "1301ae95", + "metadata": {}, + "source": [ + "2. Как оформляется тело функции в **Python**?\n", + "\n", + "- [ ] Операторами `BEGIN-END`\n", + "- [x] Отступом\n", + "- [ ] Фигурными скобками\n", + "- [ ] Квадратными скобками" + ] + }, + { + "cell_type": "markdown", + "id": "a52fd238", + "metadata": {}, + "source": [ + "3. Что произойдет при вызове функции `foo`?\n", + "\n", + "```python\n", + "def foo(*args, **kwargs): pass\n", + "```\n", + "\n", + "- [ ] Все именованные аргументы запишутся в кортеж `kwargs`\n", + "- [x] Все именованные аргументы запишутся в словарь `kwargs`\n", + "- [ ] Синтаксическая ошибка\n", + "- [x] Все позиционные аргументы запишутся в кортеж `args`\n" + ] + }, + { + "cell_type": "markdown", + "id": "2384b8b4", + "metadata": {}, + "source": [ + "4. В каком случае можно вызвать функцию без параметров?\n", + "\n", + "- [x] Если используются `*args`, `**kwargs`\n", + "- [ ] У каждой функции должны быть параметры, в этом суть функций\n", + "- [x] Если у всех аргументов есть значения по умолчанию\n", + "- [x] Если она не ожидает аргументов" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Тест по функциям.pdf b/2. Структуры данных и функции/2. Функции/Тест по функциям.pdf new file mode 100644 index 0000000..c4f83a8 Binary files /dev/null and b/2. Структуры данных и функции/2. Функции/Тест по функциям.pdf differ diff --git a/2. Структуры данных и функции/2. Функции/Файлы.ipynb b/2. Структуры данных и функции/2. Функции/Файлы.ipynb new file mode 100644 index 0000000..1386517 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Файлы.ipynb @@ -0,0 +1,378 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "be7bc6cb", + "metadata": {}, + "source": [ + "# Файлы #" + ] + }, + { + "cell_type": "markdown", + "id": "cc8b2eae", + "metadata": {}, + "source": [ + "На этой лекции мы поговорим с вами о файлах и том, как с ними работать.\n", + "\n", + "С файлами, на самом деле, в Python'е никаких проблем не должно возникнуть.\n", + "Все довольно просто. Чтобы открыть файлы мы используем встроенный метод `open`, встроенную функцию `open`, и передаем ей `filename`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5974cb39", + "metadata": {}, + "outputs": [], + "source": [ + "f = open(\"filename\")" + ] + }, + { + "cell_type": "markdown", + "id": "aee8d1e2", + "metadata": {}, + "source": [ + "У нас возвращается файловый объект, с которым мы потом можем работать, для того чтобы записывать данные или читать данные из файлов. \n", + "\n", + "Можно их открывать на запись, на чтение, на чтение и запись, и на дозапись. Делается это с помощью модов, которым мы тоже придаем функцию `open`. Например, `a` — это дозапись, `w` — это, очевидно, запись, `r` — это прочтение, `r+` — это запись и чтение одновременно. Точно так же мы можем открывать файл в бинарном виде, то есть работать с бинарными данными, и происходит все аналогично обычному типу. Мы просто добавляем в мод букву `b`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "3584b6c2", + "metadata": {}, + "outputs": [], + "source": [ + "text_modes = [\"r\", \"w\", \"a\", \"r+\"]\n", + "binary_modes = [\"br\", \"bw\", \"ba\", \"br+\"]" + ] + }, + { + "cell_type": "markdown", + "id": "83c0ce60", + "metadata": {}, + "source": [ + "Чтобы открыть файл, например, на запись, мы передаем мод `w` в функцию `open`. Чтобы записать данный файл, мы берем наш файловый объект и пользуемся методом файла `write`, передавая туда строку. В данном случае мы передаем строку, и наш метод `write` возвращает количество символов, которые мы записали. Если это будет байтовая информация, то, собственно, это будет не количество символов, а количество байт. Чтобы закрыть файл, нам нужно вызвать метод `close`, и файл нужно обязательно закрывать." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2b25c2b5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "47" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f = open(\"filename\", \"w\")\n", + "\n", + "f.write(\"The world is changed.\\nI taste it in the water.\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ebc3244a", + "metadata": {}, + "outputs": [], + "source": [ + "f.close()" + ] + }, + { + "cell_type": "markdown", + "id": "16383eec", + "metadata": {}, + "source": [ + "Если вы откроете файл и не закроете файл, может произойти что-нибудь страшное и ужасное. На самом деле нет, но принято файлы закрывать, и мы поговорим с вами о том, как с этим работать. Итак, чтобы открыть файл на чтение и запись, нам нужно использовать `r+`, и мы можем читать данные из файла с помощью метода `read`. `Read` по умолчанию читает столько, сколько сможет. Если файл не поместится в памяти, то это ваши проблемы. Вы также можете указать в методе `read` конкретное количество информации, которое вы хотите прочитать, передав `size`, но по умолчанию читается весь файл и выводится, собственно." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9d740b8c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'The world is changed.\\nI taste it in the water.\\n'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f = open(\"filename\", \"r+\")\n", + "f.read()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "aee2da15", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "49" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f.tell()" + ] + }, + { + "cell_type": "markdown", + "id": "1d4c7a2b", + "metadata": {}, + "source": [ + "Здесь мы выводим все строчки, которые туда записали, и видим, что на самом деле информация в файл попала. Очень важный момент. Когда мы прочитали весь файл, у нас указатель того, где мы сейчас находимся в файле — в самом конце. В данном случае это 47 символов. Если мы попробуем прочитать еще раз, то мы ничего не найдем, потому что мы весь файл уже прочитали и у нас пойнтер в самом конце." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cf719f78", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f.read()" + ] + }, + { + "cell_type": "markdown", + "id": "9b0798a6", + "metadata": {}, + "source": [ + "Для того чтобы прочитать его, например, заново, нам нужно использовать метод `seek` и перенести указатель на начало файла. Как вы видите, он равен нулю, и теперь мы можем действительно читать данные заново и, например, опять же закрыть файл, потому что файлы всегда нужно закрывать." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "57b6f5bb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f.seek(0)\n", + "f.tell()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4ce93652", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The world is changed.\n", + "I taste it in the water.\n", + "\n" + ] + } + ], + "source": [ + "print(f.read())\n", + "f.close()" + ] + }, + { + "cell_type": "markdown", + "id": "8109309b", + "metadata": {}, + "source": [ + "Собственно, кроме обычного метода `read`, есть также полезные методы для работы со строками, которые позволяют вам читать строки, одну, например, или несколько.\n", + "\n", + "Чтобы прочитать конкретно одну строку, можно использовать метод `readline` у того же файлового объекта, и метод вернет строку, разбив по символу переноса строки. Как видите, у нас вернулась только первая строка без второй." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1a2fd52d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The world is changed.\n", + "\n" + ] + } + ], + "source": [ + "f = open(\"filename\", \"r+\")\n", + "print(f.readline())\n", + "f.close()" + ] + }, + { + "cell_type": "markdown", + "id": "16da382f", + "metadata": {}, + "source": [ + "Если вам интересно прочитать сразу все строки, разбить их по символу переноса строки и записать все это, например, в список, можно использовать метод `readlines`. Он сделает именно это — вернет список строк, разбитых по символу перевода строки." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1d65e32e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['The world is changed.\\n', 'I taste it in the water.\\n']" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f = open(\"filename\", \"r+\")\n", + "f.readlines()" + ] + }, + { + "cell_type": "markdown", + "id": "9fb151d1", + "metadata": {}, + "source": [ + "Если мы закроем файл и попробуем его прочитать, очевидно, у нас ничего не получится, потому что закрытый файл прочитать нельзя. Будьте внимательны." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "13c0a8d3", + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "I/O operation on closed file.", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mread\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mValueError\u001b[0m: I/O operation on closed file." + ] + } + ], + "source": [ + "f.close()\n", + "f.read()" + ] + }, + { + "cell_type": "markdown", + "id": "6ac70071", + "metadata": {}, + "source": [ + "На самом деле, я рекомендую вам работать с файлами немного другим образом. Существует специальная вещь, называемая контекстный менеджер, который вам позволяет не заботиться о закрытии файлов. Вы можете открыть файл с помощью оператора `with`, и записать в файловый объект переменную `f` и потом работать с ним внутри этого контекстного блока и не закрывать его, потому что контекстный менеджер заботится об этом за вас." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "ed64d12d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The world is changed.\n", + "I taste it in the water.\n", + "\n" + ] + } + ], + "source": [ + "with open(\"filename\") as f:\n", + " print(f.read())" + ] + }, + { + "cell_type": "markdown", + "id": "7318dd16", + "metadata": {}, + "source": [ + "Про контекстный менеджер мы поговорим позднее, а пока просто можете использовать эту конструкцию и не заботиться о том, чтобы закрывать файл.\n", + "\n", + "Итак, с файлами в Python работать довольно просто. У нас есть файловые объекты, мы можем в них писать, из них читать. Не забывайте закрывать файлы или используйте контекстный менеджер." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Функции.ipynb b/2. Структуры данных и функции/2. Функции/Функции.ipynb new file mode 100644 index 0000000..c1ce7b3 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Функции.ipynb @@ -0,0 +1,741 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d8cf2624", + "metadata": {}, + "source": [ + "# Функции #" + ] + }, + { + "cell_type": "markdown", + "id": "f57336b3", + "metadata": {}, + "source": [ + "Привет. На этой лекции мы с вами поговорим о функциях и о том, как с ними работать в языке Python.\n", + "\n", + "Скорее всего, вы уже встречались с функциями в других языках программирования и знаете, что функция - это просто блок кода, который можно переиспользовать несколько раз в разных местах программы.\n", + "\n", + "Мы можем вызывать функцию с какими-то аргументами и получать значения обратно.\n", + "\n", + "Чтобы определить функцию в языке Python, нужно использовать литерал `def` и с помощью отступа определить блок кода функции. В данном случае мы определяем функцию `get_seconds`, которая просто возвращает количество секунд на данный момент." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8cb5198b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "32" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from datetime import datetime\n", + "\n", + "def get_seconds():\n", + " \"\"\"Return current seconds\"\"\"\n", + " return datetime.now().second\n", + "\n", + "get_seconds()" + ] + }, + { + "cell_type": "markdown", + "id": "11105ce4", + "metadata": {}, + "source": [ + "Обратите внимание, функцию я определяю с помощью нижнего подчеркивания по `PEP8`. А дальше у функции может идти документационная строка, которая описывает то, что, собственно, внутри функции происходит. Чтобы вернуть значения из функции, мы используем `return`, в данном случае возвращаем количество секунд, а можем не писать `return`, и тогда по умолчанию вернется `None`." + ] + }, + { + "cell_type": "markdown", + "id": "b58ccdf2", + "metadata": {}, + "source": [ + "Чтобы вызвать функцию, нужно использовать круглые скобочки, и если нужно, передать туда параметры. Чтобы получить документационную строку, можно обратиться к атрибуту `__doc__`, а имя функции получается с помощью атрибута `__name__`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9a6d8edf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Return current seconds'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_seconds.__doc__" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b148cbf7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'get_seconds'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_seconds.__name__" + ] + }, + { + "cell_type": "markdown", + "id": "e378fc9d", + "metadata": {}, + "source": [ + "Чаще всего функция определяется именно с параметрами, потому что нам важно передать какие-то значения внутри функции и работать уже с ними. Мы определяем функцию `split_tags`, которая принимает какой-то `tag_string`, в данном случае это строка с тегами, например, это `python`, `perl` и `pascal`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5ad43014", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['python', 'perl', 'pascal']" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def split_tags(tag_string):\n", + " tag_list = []\n", + " \n", + " for tag in tag_string.split(\",\"):\n", + " tag_list.append(tag.strip())\n", + " \n", + " return tag_list\n", + "\n", + "split_tags(\"python, perl, pascal\")" + ] + }, + { + "cell_type": "markdown", + "id": "a17c8dec", + "metadata": {}, + "source": [ + "Мы хотим разбить эту строку по запятым и вернуть список тегов. Эта функция, собственно, это и делает. Мы передаем в нашу функцию строку и получаем обратно список. Если мы попытаемся вызвать эту функцию без параметров, у нас выпадет ошибка `TypeError`, потому что функция ожидает параметр и не знает, что делать, если его нет." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cc8143dd", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "split_tags() missing 1 required positional argument: 'tag_string'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0msplit_tags\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m: split_tags() missing 1 required positional argument: 'tag_string'" + ] + } + ], + "source": [ + "split_tags()" + ] + }, + { + "cell_type": "markdown", + "id": "96584d83", + "metadata": {}, + "source": [ + "Как вы могли заметить, мы не указываем явно, какого типа параметры функция ожидает, потому что Python - это динамический язык.\n", + "\n", + "Если вы уже сталкивались со статически типизированными языками вроде C, вы могли видеть, что, например, в C типы аннотируются, мы явно указываем, какого типа должен быть параметр функции и какого типа возвращаемые значения.\n", + "\n", + "[Aннотация типов](Aннотация%20типов.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "6edf6fa1", + "metadata": {}, + "source": [ + "Как же передаются параметры в наши функции? Например, в статических языках, опять же, вроде C, проводится очень четкое различие между передачей по ссылке и по значению.\n", + "\n", + "В Python'е каждая переменная является связью имени с объектом в памяти. И именно это имя, именно эта ссылка на объект передается в функцию." + ] + }, + { + "cell_type": "markdown", + "id": "a080fbf3", + "metadata": {}, + "source": [ + "Таким образом, если мы определим функцию `extender`, которая принимает какой-то `source_list`, то есть исходный `list`, и новый `list` и пытается расширить `source_list` с помощью `extend_list`'а, то есть просто добавить в конец все его элементы, мы вот определяем `values` и пытаемся расширить `values` с помощью `[4, 5, 6]`, то окажется, что `values` у нас изменится." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "65c8b6ed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6]\n" + ] + } + ], + "source": [ + "def extender(source_list, extend_list):\n", + " source_list.extend(extend_list)\n", + " \n", + "values = [1, 2, 3]\n", + "extender(values, [4, 5, 6])\n", + "\n", + "print(values)" + ] + }, + { + "cell_type": "markdown", + "id": "883ff05c", + "metadata": {}, + "source": [ + "Что же произошло? Наша ссылка на объект в памяти попала в `extender`. И так как `list` является изменяемым объектом, мы можем изменить этот `list`, и объект в памяти изменился. Наш `values` в глобальном `scope` поменял свое значение. Если мы попытаемся как-то изменить неизменяемое значение, в данном случае `tuple`, то у нас ничего не получится, потому что мы передаем ссылку на объект в памяти, но объект является неизменяемым." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "2f508089", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('Guido', '31/01')\n" + ] + } + ], + "source": [ + "def replacer(source_tuple, replace_with):\n", + " source_tuple = replace_with\n", + " \n", + "user_info = (\"Guido\", \"31/01\")\n", + "replacer(user_info, (\"Larry\", \"27/09\"))\n", + "\n", + "print(user_info)" + ] + }, + { + "cell_type": "markdown", + "id": "06ae74a9", + "metadata": {}, + "source": [ + "В данном случае у нас `user_info` осталось неизменным. Однако стоит быть внимательным с изменением каких-то глобальных переменных внутри функции. Это является плохим тоном, и не стоит так программировать, потому что часто бывает не очевидно, если вы вызываете какую-то функцию, а объект в глобальном `scope` изменяется. Используйте возвращаемое значение и не путайте других программистов." + ] + }, + { + "cell_type": "markdown", + "id": "0ff0ab4f", + "metadata": {}, + "source": [ + "В Python'е также существуют именованные аргументы, которые иногда бывают полезны. Например, мы можем определить функцию `say`, которая принимает два аргумента — `greeting` и `name`, просто приветствует какого-то человека.\n", + "\n", + "Однако мы можем также передать эти параметры в другом порядке, проименовав их с помощью `name` и `greeting`. Таким образом, мы передаем вначале имя, а потом `greeting`, тем не менее у нас всё работает точно так же." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "2580a8b3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello Kitty!\n", + "Hello Kitty!\n" + ] + } + ], + "source": [ + "def say(greeting, name):\n", + " print(f\"{greeting} {name}!\")\n", + " \n", + "say(\"Hello\", \"Kitty\")\n", + "say(name=\"Kitty\", greeting=\"Hello\")" + ] + }, + { + "cell_type": "markdown", + "id": "0bbd0c13", + "metadata": {}, + "source": [ + "Немножко про область видимости. Важно понимать, что переменные, объявленные вне области видимости функции, нельзя изменять. В данном случае у нас есть глобальная переменная `result`, и мы пытаемся прибавить к ней внутри функции какое-то значение. Мы пытаемся к `result` прибавить единичку при вызове функции `increment`. У нас ничего не получается, падает ошибка, потому что мы не можем внутри функции изменять объекты из глобальной области видимости." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "1b46f831", + "metadata": {}, + "outputs": [ + { + "ename": "UnboundLocalError", + "evalue": "local variable 'result' referenced before assignment", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mUnboundLocalError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 7\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mincrement\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m\u001b[0m in \u001b[0;36mincrement\u001b[1;34m()\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mincrement\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mresult\u001b[0m \u001b[1;33m+=\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mUnboundLocalError\u001b[0m: local variable 'result' referenced before assignment" + ] + } + ], + "source": [ + "result = 0\n", + "\n", + "def increment():\n", + " result += 1\n", + " return result\n", + "\n", + "print(increment())" + ] + }, + { + "cell_type": "markdown", + "id": "cc5fa177", + "metadata": {}, + "source": [ + "И очень важно это соблюдать опять же по той же самой причине. Если мы внутри функции изменяем какие-то глобальные объекты, очень часто это не очевидно. У нас есть глобальная переменная, и вызов каких-то функций меняет ее значение. Совершенно непонятно, что это происходит и часто приводит к запутанному коду." + ] + }, + { + "cell_type": "markdown", + "id": "5a903105", + "metadata": {}, + "source": [ + "Существует в Python'е возможность изменять глобальные переменные с помощью `global`, например, или `non local`, но я не рекомендую вам использовать эти особенности." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "f60a05d7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n" + ] + } + ], + "source": [ + "result = 0\n", + "\n", + "def increment():\n", + " global result\n", + " \n", + " result += 1\n", + " return result\n", + "\n", + "print(increment())" + ] + }, + { + "cell_type": "markdown", + "id": "4fe3001d", + "metadata": {}, + "source": [ + "Существует также возможность использовать аргументы по умолчанию вашей функции. Часто у вас бывают какие-то аргументы, которые можно передавать, а можно не передавать. И у них, у этих аргументов, могут быть какие-то дефолтные значения. В данном случаем мы можем приветствовать человека, если передано имя, а можем просто вывести какую-то дефолтную фразу." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "28ec1b28", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, it's me...\n" + ] + } + ], + "source": [ + "def greeting(name=\"it's me...\"):\n", + " print(f\"Hello, {name}\")\n", + " \n", + "greeting()" + ] + }, + { + "cell_type": "markdown", + "id": "be47e975", + "metadata": {}, + "source": [ + "Однако, стоит быть внимательным с аргументами по умолчанию, если мы используем в качестве аргументов по умолчанию изменяемые значения. Обратите внимание на пример.\n", + "\n", + "У нас есть функция `append_one`, и в качестве дефолтного значения, значения по умолчанию `iterable`, у нас используется список.\n", + "\n", + "Собственно `append_one` просто прибавляет единичку к переданному списку или дефолтному списку. То есть у нас возвращается либо наш исходный список плюс один, либо просто список из единички. По крайней мере так мы ожидаем." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4723d072", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 1]\n" + ] + } + ], + "source": [ + "def append_one(iterable=[]):\n", + " iterable.append(1)\n", + " return iterable\n", + "\n", + "print(append_one([1]))" + ] + }, + { + "cell_type": "markdown", + "id": "abbde05d", + "metadata": {}, + "source": [ + "Если мы передадим в `append_one` список из единицы, нам вернется две единички, что мы и ожидаем. Однако что же произойдет, если мы вызовем `append_one` два раза?" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "84b2ef43", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1]\n", + "[1, 1]\n" + ] + } + ], + "source": [ + "print(append_one())\n", + "print(append_one())" + ] + }, + { + "cell_type": "markdown", + "id": "db6f038f", + "metadata": {}, + "source": [ + "Вначале, как мы и ожидаем, к нам вернется единичка, потому что мы взяли наше дефолтное значение, добавили единичку в список и вернули. Однако если мы вызовем во второй раз, окажется, что у нас уже две единички, хотя мы явно ожидаем одну.\n", + "\n", + "Чтобы понять, почему это так, можно посмотреть на дефолтное значение нашей функции, и окажется, что там уже содержатся эти самые две единички. Почему так происходит?\n", + "\n", + "При определении функции, когда интерпретатор Python'а бежит по вашему файлу с кодом, определяется связь между именем функции и дефолтными значениями. Таким образом, у каждой функции появляется словарь, появляется `tuple` какой-то с дефолтными значениями. И именно в эти переменные каждый раз и происходит запись. Таким образом, если дефолтные значения являются изменяемыми, в них можно записывать, потому что это обычные переменные." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "88a2eab2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "([1, 1],)\n" + ] + } + ], + "source": [ + "print(append_one.__defaults__)" + ] + }, + { + "cell_type": "markdown", + "id": "da7c8a62", + "metadata": {}, + "source": [ + "Что же нужно делать в таком случае? Нужно определять дефолтные значения как `None`. И если нам не передан какой-то параметр, мы просто создаем новый список на лету. Можно это делать двумя механизмами." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "166799cb", + "metadata": {}, + "outputs": [], + "source": [ + "def function(iterable=None):\n", + " if iterable is None:\n", + " iterable = []\n", + " \n", + "def function(iterable=None):\n", + " iterable = iterable or []" + ] + }, + { + "cell_type": "markdown", + "id": "fe32e50c", + "metadata": {}, + "source": [ + "Очень красивой особенностью Python'а является возможность определения функции, которая принимает разные количества аргументов.\n", + "\n", + "Мы можем определить функцию `printer`, которая принимает разное количество аргументов. Может быть один, два, три, четыре, сотня аргументов.\n", + "\n", + "В данном случае все аргументы записываются в `tuple args`. И мы видим, что это действительно `tuple`, если мы выведем тип. И мы можем, например, эти аргументы как-то потом использовать, можем просто по ним как-то пробежаться и вывести их." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "5a5112e7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "1\n", + "2\n", + "3\n", + "4\n", + "5\n" + ] + } + ], + "source": [ + "def printer(*args):\n", + " print(type(args))\n", + " \n", + " for argument in args:\n", + " print(argument)\n", + "\n", + "printer(1, 2, 3, 4, 5)" + ] + }, + { + "cell_type": "markdown", + "id": "2552db47", + "metadata": {}, + "source": [ + "Точно так же можно разворачивать наш список в аргументах. Мы можем определить наш список с Джоном, Биллом и Эми и передать наш список как аргументы в нашу функцию." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "b458e59b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "John\n", + "Bill\n", + "Amy\n" + ] + } + ], + "source": [ + "name_list = [\"John\", \"Bill\", \"Amy\"]\n", + "printer(*name_list)" + ] + }, + { + "cell_type": "markdown", + "id": "6a2299cf", + "metadata": {}, + "source": [ + "Таким образом, первым аргументом станет Джон, вторым — Билл, и третьим — Эми.\n", + "\n", + "Это так называемая распаковка списка. Есть еще подобные способы использования:" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "21a58d1d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 2 [3, 4, 5]\n" + ] + } + ], + "source": [ + "a, b, *others = [1, 2, 3, 4, 5]\n", + "\n", + "print(a, b, others)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "1bdc2fa4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 2 [3, 4] 5\n" + ] + } + ], + "source": [ + "a, b, *others, c = [1, 2, 3, 4, 5]\n", + "\n", + "print(a, b, others, c)" + ] + }, + { + "cell_type": "markdown", + "id": "cf3a0381", + "metadata": {}, + "source": [ + "Точно так же это работает в случае со словарями, в данном случае мы можем определить функцию `printer`, которая принимает разное количество именованных аргументов." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "8ade7f94", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a 10\n", + "b 11\n" + ] + } + ], + "source": [ + "def printer(**kwargs):\n", + " print(type(kwargs))\n", + " for key, value in kwargs.items():\n", + " print(f\"{key} {value}\")\n", + " \n", + "printer(a=10, b=11)" + ] + }, + { + "cell_type": "markdown", + "id": "5a455966", + "metadata": {}, + "source": [ + "Собственно, все записывается в `kwargs`, и таким образом `kwargs` у нас останется `dict`'ом. Если мы передадим два именованных аргумента, `a = 10` и `b = 11`, то у нас получится словарь, и мы можем потом этот словарь как-то использовать, используя параметры и, например, просто их выводя на экран.\n", + "\n", + "Точно так же мы можем разыменовывать, разворачивать эти словари в обратную сторону. Таким образом, если у нас есть словарь, мы можем передавать значения из этого словаря как аргументы, именованные, в нашу функцию. Таким образом, когда мы используем две звездочки при вызове функции, у нас первым параметром становится `user_id`, а вторым параметром становится `feedback`. Это используется практически везде и позволяет вам определять очень гибкие функции, которые принимают различное количество аргументов — именованных и позиционных." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d4989f77", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "user_id 117\n", + "feedback {'subject': 'Registration fields', 'message': 'There is no country for old men'}\n" + ] + } + ], + "source": [ + "payload = {\n", + " \"user_id\": 117,\n", + " \"feedback\": {\n", + " \"subject\": \"Registration fields\",\n", + " \"message\": \"There is no country for old men\"\n", + " }\n", + "}\n", + "\n", + "printer(**payload)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/2. Функции/Функциональное программирование.ipynb b/2. Структуры данных и функции/2. Функции/Функциональное программирование.ipynb new file mode 100644 index 0000000..9b55cf9 --- /dev/null +++ b/2. Структуры данных и функции/2. Функции/Функциональное программирование.ipynb @@ -0,0 +1,909 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "46044c32", + "metadata": {}, + "source": [ + "# Функциональное программирование #" + ] + }, + { + "cell_type": "markdown", + "id": "b1d0b1e3", + "metadata": {}, + "source": [ + "Мы с вами разобрали то, как работают и определяются функции в Python'e и настало время разобраться с таким сложным концептом как функциональное программирование.\n", + "\n", + "Что же это такое? Чтобы понять, что такое функциональное программирование, нужно понимать несколько особенностей.\n", + "\n", + "Во-первых, функции в Python'e - это такие же объекты, как и, например, строки, списки или классы.\n", + "Их можно передавать в функции другие, их можно возвращать из функций, их можно создавать на лету, то есть это объекты первого класса.\n", + "Например, мы можем определить функцию `caller`, которая принимает другую функцию." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "dd050baa", + "metadata": {}, + "outputs": [], + "source": [ + "def caller(func, params):\n", + " return func(*params)" + ] + }, + { + "cell_type": "markdown", + "id": "60905ede", + "metadata": {}, + "source": [ + "Функцию можно передавать в функцию, как я вам говорил. Она принимает другую функцию и какой-то список параметров, и, собственно, эта функция вызывается этим списком параметров с использованием звездочки, с которой мы только что с вами разобрались.\n", + "\n", + "Определим другую функцию — обычную `printer`, которая просто выводит какую-то строчку. Мы можем в наш `caller` передать функцию `printer` и вызвать её. Это может быть функция `printer` в нашем `caller`'е, а может быть какая-нибудь другая функция - нам неважно." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "bcfb80f1", + "metadata": {}, + "outputs": [], + "source": [ + "def printer(name, origin):\n", + " print(f\"I'm {name} of {origin}!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5c79161f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "I'm Moana of Motunui!\n" + ] + } + ], + "source": [ + "caller(printer, [\"Moana\", \"Motunui\"])" + ] + }, + { + "cell_type": "markdown", + "id": "29db28ad", + "metadata": {}, + "source": [ + "Главное, чтобы мы передали туда функцию и список параметров. Итак, мы вызываем наш `caller` с функцией `printer` и передаем туда список параметров и у нас выводится строка.\n", + "\n", + "Собственно, главное, что нужно вынести из этого примера, что функции можно передавать другие функции. Это такие же объекты." + ] + }, + { + "cell_type": "markdown", + "id": "4aae0263", + "metadata": {}, + "source": [ + "Ещё один важный момент. Функции можно не только передавать, их еще можно создавать внутри других функций. Например, мы можем определить функцию `get_multiplier` и внутри функции `get_multiplier` определить другую функцию, и эта функция будет `inner`. Она будет просто умножать два аргумента ей переданных." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "77503f82", + "metadata": {}, + "outputs": [], + "source": [ + "def get_multiplier():\n", + " def inner(a, b):\n", + " return a * b\n", + " \n", + " return inner" + ] + }, + { + "cell_type": "markdown", + "id": "95090fff", + "metadata": {}, + "source": [ + "Мы можем вызвать функцию `get_multiplier` и получить обычный `multiplier`, который умножает, например, 10 на 11, то есть мы определили функцию, которая возвращает другую функцию." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "644b0061", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "110" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multiplier = get_multiplier()\n", + "multiplier(10, 11)" + ] + }, + { + "cell_type": "markdown", + "id": "2a1490a6", + "metadata": {}, + "source": [ + "Пока ничего сложного. Обратите внимание, что именно `multiplier`'ы — это `inner`, потому что мы вернули новую функцию и `multiplier` перестал быть тем, каким мы его знали раньше. Он стал `inner`'ом. " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "2978a144", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "inner\n" + ] + } + ], + "source": [ + "print(multiplier.__name__)" + ] + }, + { + "cell_type": "markdown", + "id": "18d1ca79", + "metadata": {}, + "source": [ + "Гораздо более интересный момент - это если мы наш `get_multiplier` что-нибудь передадим. Давайте попробуем определить функцию `inner`, которая будет принимать один аргумент и умножать она его будет всегда на то самое число, которое мы передали в `get_multiplier`." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "3c7d8cef", + "metadata": {}, + "outputs": [], + "source": [ + "def get_multiplier(number):\n", + " def inner(a):\n", + " return a * number\n", + " \n", + " return inner" + ] + }, + { + "cell_type": "markdown", + "id": "f00d88e1", + "metadata": {}, + "source": [ + "Смотрите, мы передаем `get_multiplier` двойку и получаем функцию, которая всегда умножает переданный ей аргумент на двойку." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "06513593", + "metadata": {}, + "outputs": [], + "source": [ + "multiplier_by_2 = get_multiplier(2)" + ] + }, + { + "cell_type": "markdown", + "id": "840f794a", + "metadata": {}, + "source": [ + "Например, мы передали в `multiplier_by_2` десятку и получили 20." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "256d8b08", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multiplier_by_2(10)" + ] + }, + { + "cell_type": "markdown", + "id": "e72a8eb0", + "metadata": {}, + "source": [ + "Эта концепция называется \"замыканием\". В данном случае у нас используется `number` из скопа выше, из области видимости функции `get_multiplier` и это очень важный момент, который в дальнейшем будет использоваться, например, в декораторах.\n", + "\n", + "Часто бывает необходимо определить какие-то свои функции, которые принимают другие функции, но ещё чаще эти функции используют примерно одинаково, например, для того, чтобы применить какую-то функцию к набору элементов.\n", + " \n", + "Существует несколько стандартных функций, которые вам позволяют в функциональном стиле работать.\n", + "\n", + "Одна из таких функций - это `map`. Функция `map` принимает функцию и какой-то итерабельный объект, то есть, например, список или что-то, по чему можно итерироваться, и применяет полученную функцию к этому итерабельному объекту.\n", + "\n", + "Например, мы можем определить функцию `squarify`, которая просто принимает число и возвращает вторую степень его, то есть возвращает квадрат." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "7460b616", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 1, 4, 9, 16]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def squarify(a):\n", + " return a ** 2\n", + "\n", + "list(map(squarify, range(5)))" + ] + }, + { + "cell_type": "markdown", + "id": "3fded733", + "metadata": {}, + "source": [ + "Функция `map` работает очень просто. Мы передаем ей функцию `squarify` и `range`, то есть итератор от ноля до четырех. Функция `map` бежит по этому итерабельному объекту, то есть по `rang`'у, и применяет `squarify` к элементу одному за другим. В данном случае у нас получается список от 0 до 16 квадратов. Обратите внимание, я вызываю функцию `list` вокруг `map`'а, потому что `map` по умолчанию возвращает `map object`. Это не список, а какой-то объект внутренний, которому самое главное, что нужно — итерироваться.\n", + "\n", + "Очень часто нам просто необходимо итерироваться по этому объекту, нам не важно конкретно, чтобы это был список. Собственно, что здесь происходит, можно посмотреть на чистом Python'e. У нас как бы в начале создается `squared_list`, и потом просто мы в цикле в этот `squared_list` записываем применение функции к текущему `number`'у.\n", + "\n", + "Однако, это делается всё довольно просто и коротко с помощью функции `map`. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "726ace85", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 4, 9, 16]\n" + ] + } + ], + "source": [ + "squared_list = []\n", + "\n", + "for number in range(5):\n", + " squared_list.append(squarify(number))\n", + " \n", + "print(squared_list)" + ] + }, + { + "cell_type": "markdown", + "id": "6518fd00", + "metadata": {}, + "source": [ + "Ещё одна функция, которая часто используется в контексте функционального программирования, это функция `filter`. Функция `filter` вам позволяет фильтровать по какому-то предикату итерабельный объект.\n", + "\n", + "У вас есть какое-то условие, в данном случае — это функция, которая принимает число и проверяет, что это число больше ноля и возвращает `True` в этом случае, если это ноль или меньше, то возвращается `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "ee4c3b6f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def is_positive(a):\n", + " return a > 0\n", + "\n", + "list(filter(is_positive, range(-2, 3)))" + ] + }, + { + "cell_type": "markdown", + "id": "4442abda", + "metadata": {}, + "source": [ + "У нас `filter` принимает какую-то функцию и фильтрует все аргументы внутри итерабельного объекта по этому предикату. Например, мы можем оставить в нашем списке, или в нашем `rang`'е, в нашем итерабельном объекте только положительные числа. \n", + "\n", + "Собственно, на чистом Python'e это выглядело бы как-то так. У нас есть наш `positive_list` и мы в цикле просто проверяем, что если у нас функция возвращает `True`, то мы оставляем это значение, то есть прибавляем в `positive_list` это число. Иначе мы просто продолжаем итерацию и ничего не делаем. Можно убедиться, что выводится именно то же самое." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "ae7405d5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2]\n" + ] + } + ], + "source": [ + "positive_list = []\n", + "\n", + "for number in range(-2, 3):\n", + " if is_positive(number):\n", + " positive_list.append(number)\n", + " \n", + "print(positive_list)" + ] + }, + { + "cell_type": "markdown", + "id": "d5f9f452", + "metadata": {}, + "source": [ + "`Filter` и `map` позволяет вам лаконично и просто записать стандартные какие-то паттерны программирования." + ] + }, + { + "cell_type": "markdown", + "id": "02a5ad50", + "metadata": {}, + "source": [ + "Сейчас мы с вами разберем анонимные функции. Хотелось бы заметить, что несмотря на то, что `map` и `filter` очень мощны, не стоит злоупотреблять ими, потому что, если вы пишете довольно сложный код, который сложно разобрать, если вы вкладываете `map` в `map`, и `filter` в `filter`, пишете многострочные какие-то функциональные штуки, программистам сложно будет читать этот код. \n", + "\n", + "Чем сильнее, чем выше когнитивная нагрузка у программиста, тем хуже. Пожалуйста, не злоупотребляйте ими, хотя это довольно мощные конструкции.\n", + "\n", + "Итак, часто нам бывает необходимо определить какую-то функцию, например, `squarify` как мы определяли, но нам не нужно будет её применять в дальнейшем. Нам она интересна только в каком-то контексте. Например, возвести во вторую степень все элементы в этом списке, но мы не хотим использовать эту функцию потом. Нам на помощь в данном случае придут анонимные функции, так они называются в Python'e или `lambda` функции. `Lambda` позволяет вам определить функцию прям `in place`, то есть без оператора `def`, без литерала `def`. Используйте её прям на месте и забыли про нее потом. В данном случае мы делаем точно то же самое, что и в предыдущем примере, только определяем функцию не с помощью `def`'а, где-то вверху, а прямо на месте и пишем, что наша анонимная функция принимает один параметр и возводит его в квадрат. Всё." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "312fd40e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 1, 4, 9, 16]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(map(lambda x: x ** 2, range(5)))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "01b7bc1c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "function" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(lambda x: x ** 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "5d3224c3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(filter(lambda x: x > 0, range(-2, 3)))" + ] + }, + { + "cell_type": "markdown", + "id": "08af4100", + "metadata": {}, + "source": [ + "Происходит то же самое, только у нас функция создается `in place`. Собственно, как видите, `map` точно так же принимает функцию и итерабельный объект. Ничего нового. `Lambda` — это действительно функция, но она анонимная. У нее нет имени, как видите, просто какая-то функция, принимающая один объект и возвращающая квадрат. То же самое можно сделать с `filter`'ом, и `filter` у нас примет функцию `lambda`, и `lambda` проверит, что число больше нуля." + ] + }, + { + "cell_type": "markdown", + "id": "0f87bbbf", + "metadata": {}, + "source": [ + "Давайте в качестве примера напишем функцию, которая превращает список чисел в список строк. Сделайте это самостоятельно." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "0977cbb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def stringify_list(num_list):\n", + " return list(map(str, num_list))\n", + "\n", + "stringify_list(range(10))" + ] + }, + { + "cell_type": "markdown", + "id": "1a27fac1", + "metadata": {}, + "source": [ + "Отлично, скорее всего у вас всё получилось. Ничего сложного не должно быть. Чтобы превратить список чисел в список строк нам нужно использовать функцию `map`, как вы могли догадаться, и передать функцию `map`, класс `str`. При применении класса `str` к числу, у нас получается строка и именно это нам и нужно. Нам нужно пробежаться по списку и всё передать в строку, то есть передать в конструктор `str`. Мы можем вызвать нашу функцию в `range` и получить список строк от нуля до девяти." + ] + }, + { + "cell_type": "markdown", + "id": "a31320c1", + "metadata": {}, + "source": [ + "Существует отличный модуль `functools`, который вам позволяет использовать функциональные особенности Python'а ещё лучше.\n", + "\n", + "Например, `functools` в последних версиях языка принесли функцию `reduce`, которая изначально была в общем скопе глобальных функций. Что делает `reduce`? `Reduce`, как вы уже могли догадаться, принимает функцию и итерабельный объект. Что она делает с ним? В данном случае у нас функция `multiply`, которая принимает два значения и перемножает их. Функция `reduce`, начиная слева направо, бежит и по цепочке применяет эту функцию." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "6007f8fc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "120" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from functools import reduce\n", + "\n", + "def multiply(a, b):\n", + " return a * b\n", + "\n", + "reduce(multiply, [1, 2, 3, 4, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "28512684", + "metadata": {}, + "source": [ + "Каким образом? В начале в функцию `multiply` попадают один и два, они перемножаются и запоминается двойка, у нас в памяти лежит двойка. Дальше, у нас эта двойка перемножается с тройкой. Таким образом у нас была двойка в памяти, берется следующий элемент, перемножается с тройкой. У нас уже шесть в памяти. Шесть в памяти умножается на четыре. У нас получается 24 и запоминается. 24 потом уже умножается на пять, получается 120, что мы получаем на входе. `Reduce` позволяет вам сжимать какие-то данные, применяя последовательно функцию и запоминая результат. То же самое можно сделать с помощью анонимных функций." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "634a1fde", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "24" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "reduce(lambda x, y: x * y, range(1, 5))" + ] + }, + { + "cell_type": "markdown", + "id": "6fad411a", + "metadata": {}, + "source": [ + "Да, но получилось не 120 — `range`, как вы могли догадаться, пятерку не включает." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "377ef58d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "120" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "reduce(lambda x, y: x * y, range(1, 6))" + ] + }, + { + "cell_type": "markdown", + "id": "0b35a963", + "metadata": {}, + "source": [ + "Также в `functools` есть отличный метод `partial`, который вам позволяет модифицировать немного поведение функций. Например, у нас есть функция `greeter`, которая просто приветствует человека с помощью какого-то приветствия `greeting`. Например, может поздороваться с Майклом. И мы можем определить какой-то новый набор функций, подменив по умолчанию определенные параметры. " + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "9dfec3a9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hi, brother!\n", + "Hello, sir!\n" + ] + } + ], + "source": [ + "from functools import partial\n", + "\n", + "def greeter(person, greeting):\n", + " return f\"{greeting}, {person}!\"\n", + "\n", + "hier = partial(greeter, greeting=\"Hi\")\n", + "helloer = partial(greeter, greeting=\"Hello\")\n", + "\n", + "print(hier(\"brother\"))\n", + "print(helloer(\"sir\"))" + ] + }, + { + "cell_type": "markdown", + "id": "737347b9", + "metadata": {}, + "source": [ + "Например, мы можем создать новую функцию `hier`, которая будет всегда приветствовать с помощью приветствия \"Hi\". Делается это с помощью функции `partial`, которая принимает, опять же, функцию и какой-то параметр, который нужно подменить. В данном случае у нас будет функция `hier` и функция `helloer`. `helloer` всегда приветствует с помощью \"Hallo\", а `hier` — всегда с помощью \"Hi\"." + ] + }, + { + "cell_type": "markdown", + "id": "9c07a270", + "metadata": {}, + "source": [ + "Например, у нас формальное и неформальное приветствие. Таким образом, у нас была функция `greeter`, которая принимает `greeting` и `person`, а стала только функция `hier` и `helloer`, которая принимает только `person`. Например, мы можем поздороваться неформально со своим братом и с каким-то незнакомым мужчиной с помощью \"Hallo\". Функция `partial` позволяет вам подменять определенные аргументы и модифицировать поведение функций. Например, мы можем определить логер, который записывает информацию только в какой-то конкретный файл или любые действия на ваш вкус." + ] + }, + { + "cell_type": "markdown", + "id": "5fe2ecb1", + "metadata": {}, + "source": [ + "Списочные выражения, очень важная концепция, которая используется практически повсеместно, потому, что позволяет вам упростить жизнь, и написать красивый код. Раньше, чтобы создать список, например, квадратов, мы с вами писали цикл. Создавали список и в цикле в этот список добавляли элементы — квадраты числа. Получалось довольно просто, однако нам необходимо определять список, определять цикл и в цикле ещё делать `append`.\n", + "\n", + "Это довольно многословно, в Python-е так не годится, и поэтому есть более простой, удобный метод написать то же самое. Этот метод называется \"списочное выражение\", или `list comprehensions`, которое выглядит так. У нас есть квадратные скобочки, и мы в квадратных скобочках пишем цикл. У нас цикл по `range`-у, мы берем `number`, этот `number` возводим в квадрат, и присваиваем его в список. Таким образом у нас список наполняется с помощью вот таких элементов. В цикле у нас добавляются квадраты числа и получается точно такой же результат — это называется \"списочное выражение\", оно работает немного быстрее, чем такой вот цикл и выглядит намного лаконичнее и короче. Поэтому практически всегда используются именно они, именно \"списочные выражения\"." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "606fba9b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n" + ] + } + ], + "source": [ + "square_list = []\n", + "\n", + "for number in range(10):\n", + " square_list.append(number ** 2)\n", + " \n", + "print(square_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "e6076d78", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n" + ] + } + ], + "source": [ + "square_list = [number ** 2 for number in range(10)]\n", + "print(square_list)" + ] + }, + { + "cell_type": "markdown", + "id": "2d240574", + "metadata": {}, + "source": [ + "Точно так же можно написать списочное выражение с каким-то условием. А если нам нужны только четные числа в нашем списке, мы бы писали что-то в этом роде. У нас был бы цикл и условия, и мы только при выполнении условия бы добавляли в наш список элемент. Мы можем точно так же добавить условие в `list comprehensions`, и добавлять наш `num` только при выполнении условия остаток от деления равен нулю." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "fa856e67", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 2, 4, 6, 8]\n" + ] + } + ], + "source": [ + "even_list = []\n", + "\n", + "for number in range(10):\n", + " if number % 2 == 0:\n", + " even_list.append(number)\n", + " \n", + "print(even_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "d417cb66", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 2, 4, 6, 8]\n" + ] + } + ], + "source": [ + "even_list = [num for num in range(10) if num % 2 == 0]\n", + "print(even_list)" + ] + }, + { + "cell_type": "markdown", + "id": "9625d939", + "metadata": {}, + "source": [ + "Точно так же мы можем определять словари. Например, с помощью фигурных скобочек и двоеточия у нас соответственно в словаре, это ключ, и number в квадрате — это значение. Например, у нас строится отображение из числа в его квадрат. Мы можем определять точно так же с помощью `dict comprehensions` \"дикты\"." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "e584a051", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}\n" + ] + } + ], + "source": [ + "square_map = {number: number ** 2 for number in range(5)}\n", + "print(square_map)" + ] + }, + { + "cell_type": "markdown", + "id": "2edd7643", + "metadata": {}, + "source": [ + "Мы можем делать с помощью обычных фигурных скобочек без двоеточий делать `set`-ы." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "04d502db", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}\n" + ] + } + ], + "source": [ + "reminders_set = {num % 10 for num in range(100)}\n", + "print(reminders_set)" + ] + }, + { + "cell_type": "markdown", + "id": "2cbba536", + "metadata": {}, + "source": [ + "Обратите внимание, это опять же довольно мощные конструкции, которые здорово и легко использовать, однако, например, `list comprehensions`-ы позволяют вам вкладывать циклы `for` друг в друга и строить какие-то более сложные вещи.\n", + "\n", + "Например, вы можете в условиях писать сложные предикаты. " + ] + }, + { + "cell_type": "markdown", + "id": "e276ea97", + "metadata": {}, + "source": [ + "Пожалуйста не делайте этого, держите ваш код простым и понятным. Несмотря на то, что функциональные особенности языка очень легко использовать и писать довольно большие многострочные выражения, лучше, чтобы ваш код был простым и понятным. Не злоупотребляйте этим. Ну и что интересно, если мы просто без скобочек напишем такое выражение, то у нас тоже что-то получится. И на самом деле это генератор. О генераторах мы поговорим позже. Пока просто знайте, что это какой-то объект, в котором, например, можно итерироваться. Но это не список, не `tuple`, и не `set`." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "4d02e541", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(type(number ** 2 for number in range(5)))" + ] + }, + { + "cell_type": "markdown", + "id": "abb2a048", + "metadata": {}, + "source": [ + "Ещё одна важная функция, которая часто рассматривается в контексте функционального программирования, это функция `zip`. Функция `zip` позволяет вам склеить два итерабельных объекта. В данном случае у нас есть `numList`, `squaredList`, представляющий собой квадраты чисел, и мы склеиваем их. И у нас первый элемент одного списка с первым элементом других списков попадает в один `tuple`. Происходит что-то в этом роде. Часто бывает функция эта полезна." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "67c6fbd2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36)]" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num_list = range(7)\n", + "squared_list = [x ** 2 for x in num_list]\n", + "\n", + "list(zip(num_list, squared_list))" + ] + }, + { + "cell_type": "markdown", + "id": "53cb437e", + "metadata": {}, + "source": [ + "Итак. Мы с вами разобрали такую страшную концепцию, как функциональное программирование, которая на самом деле проста.\n", + "\n", + "Мы с вами узнали, что функции - это объекты первого класса, их можно передавать внутри функций, возвращать из функций, создавать внутри функций, возвращать, то есть работать с ними как с обычными объектами; разобрали с вами классические функции `map`, `filter` и `reduce`; посмотрели на анонимные функции, которые можно создавать `in place` и разобрали списочные выражения, которые часто вам могут упростить жизнь для того, чтобы создавать списки, `set`-ы, `dict`-ы, или что-то в этом роде." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/2. Структуры данных и функции/Readme.ipynb b/2. Структуры данных и функции/Readme.ipynb new file mode 100644 index 0000000..71a0a0d --- /dev/null +++ b/2. Структуры данных и функции/Readme.ipynb @@ -0,0 +1,124 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "69d5ed93", + "metadata": {}, + "source": [ + "# Структуры данных и функции #" + ] + }, + { + "cell_type": "markdown", + "id": "325dbb76", + "metadata": {}, + "source": [ + "На этом блоке вы узнаете про новые типы данных — коллекции, познакомитесь с функциями, а так же научитесь использовать функциональное программирование в Python." + ] + }, + { + "cell_type": "markdown", + "id": "9f938f9d", + "metadata": {}, + "source": [ + "## Задачи обучения ##" + ] + }, + { + "cell_type": "markdown", + "id": "acd18a27", + "metadata": {}, + "source": [ + "- Научиться работать со стандартными структурами данных в Python.\n", + "- Научиться писать функции на Python.\n", + "- Применять функциональные особенности языка.\n", + "- Научиться работать с файлами с помощью языка Python." + ] + }, + { + "cell_type": "markdown", + "id": "f70a94ad", + "metadata": {}, + "source": [ + "## Оглавление ##" + ] + }, + { + "cell_type": "markdown", + "id": "ae73a075", + "metadata": {}, + "source": [ + "### Коллекции ###" + ] + }, + { + "cell_type": "markdown", + "id": "6f650a78", + "metadata": {}, + "source": [ + "- [Списки и кортежи](1.%20Коллекции/Списки%20и%20кортежи.ipynb)\n", + "- [Списки. Пример программы](1.%20Коллекции/Списки.%20Пример%20программы.ipynb)\n", + "- [Словари](1.%20Коллекции/Словари.ipynb)\n", + "- [Словари. Пример программы](1.%20Коллекции/Словари.%20Пример%20программы.ipynb)\n", + "- [Множества](1.%20Коллекции/Множества.ipynb)\n", + "- [Множества. Пример программы](1.%20Коллекции/Множества.%20Пример%20программы.ipynb)\n", + "- [Документация](1.%20Коллекции/Документация.ipynb)\n", + "- [Тест по коллекциям](1.%20Коллекции/Тест%20по%20коллекциям.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "41f515e3", + "metadata": {}, + "source": [ + "### Функции ###" + ] + }, + { + "cell_type": "markdown", + "id": "d9c03ccd", + "metadata": {}, + "source": [ + "- [Функции](2.%20Функции/Функции.ipynb)\n", + "- [Тест по функциям](2.%20Функции/Тест%20по%20функциям.ipynb)\n", + "- [Файлы](2.%20Функции/Файлы.ipynb)\n", + "- [Функциональное программирование](2.%20Функции/Функциональное%20программирование.ipynb)\n", + "- [Декораторы](2.%20Функции/Декораторы.ipynb)\n", + "- [Генераторы](2.%20Функции/Генераторы.ipynb)\n", + "- [Документация](2.%20Функции/Документация.ipynb)\n", + "- [Задание. Декоратор to_json](2.%20Функции/Задание.%20Декоратор%20to_json.ipynb)\n", + "- [Задание. Key-value хранилище](2.%20Функции/Задание.%20Key-value%20хранилище.ipynb)\n", + "- [Тест по блоку](2.%20Функции/Тест%20по%20блоку.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "87e687a1", + "metadata": {}, + "source": [ + "Вот и закончился второй блок нашего курса. Мы с вами разобрали типы даных коллекций, научились работать с функциями, узнали что такое функциональное программирование, как задавать свои собственные декораторы и генераторы. На следующем блоке вы будете изучать объектно-ориентрованное программирование в Python'е. [Далее...](../3.%20Объектно-ориентированное%20программирование/Readme.ipynb)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/1. Классы и объекты/Документация.ipynb b/3. Объектно-ориентированное программирование/1. Классы и объекты/Документация.ipynb new file mode 100644 index 0000000..36da29f --- /dev/null +++ b/3. Объектно-ориентированное программирование/1. Классы и объекты/Документация.ipynb @@ -0,0 +1,46 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "56227466", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "a63a522c", + "metadata": {}, + "source": [ + "- [Описание классов в документации Python 3.](https://docs.python.org/3.6/tutorial/classes.html \"9. Classes\")\n", + "- [Очень хорошая вводная статья на английском про классы.](https://www.python-course.eu/python3_object_oriented_programming.php \"Object-Oriented Programming\")\n", + "Также с того же ресурса [статья с примерами про атрибуты, @classmethod, @staticmethod.](https://www.python-course.eu/python3_class_and_instance_attributes.php \"Class and Instance Attributes\")\n", + "[И там же про @property.](https://www.python-course.eu/python3_properties.php \"Properties vs. Getters and Setters\")\n", + "\n", + "На русском языке [хорошая статья нашлась на Wikipedia](https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BD%D0%B0_Python \"Объектно-ориентированное программирование на Python\"). Обратите внимание, что помимо основ она содержит материалы, которые мы подробно осветим далее в курсе." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/1. Классы и объекты/Классы и экземпляры.ipynb b/3. Объектно-ориентированное программирование/1. Классы и объекты/Классы и экземпляры.ipynb new file mode 100644 index 0000000..e45f733 --- /dev/null +++ b/3. Объектно-ориентированное программирование/1. Классы и объекты/Классы и экземпляры.ipynb @@ -0,0 +1,1197 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cbc60620", + "metadata": {}, + "source": [ + "# Классы и экземпляры #" + ] + }, + { + "cell_type": "markdown", + "id": "8a6d683a", + "metadata": {}, + "source": [ + "На этой лекции мы начнем знакомиться с классами и с экземплярами классов.\n", + "\n", + "Надеюсь, вы, так или иначе, сталкивались с объектно-ориентированным программированием, когда работали с другими языками программирования. Но, даже если нет, ничего страшного. Примеры, которые я буду показывать, достаточно просты, чтобы вы смогли понять.\n", + "\n", + "На самом деле, в объектно-ориентированном программировании ничего сложного нет. По сути, это просто вопрос организации кода. \n", + "\n", + "Вообще, зачем нужны классы? Часто, когда говорят о классах, говорят, что их используют тогда, когда нужно отобразить реальные предметы, вещи на программный код. Отчасти это так, но я бы сказал, что классы нужны для того, чтобы объединить функционал какой-то, связанный общей идеей и смыслом, в одну сущность, причем у этой сущности может быть свое внутреннее состояние, а также методы, которые позволяют это состояние модифицировать.\n", + "\n", + "Про методы класса мы также с вами будем говорить. Какие можно привести реальные примеры использования классов? Например, это может быть обертка над соединением к базе данных. У нас есть класс, в котором есть состояние. И это состояние — это постоянное TCP соединение с базой. Также у этого класса могут быть методы, которые предоставляют некий интерфейс доступа к этому TCP соединению. Методы представляют собой, например, возможности выбрать какие-то данные из базы, либо положить какие-то данные в базу. Тем самым мы инкапсулируем, то есть скрываем, TCP соединение внутри класса, а пользователю нашего класса предоставляем удобный интерфейс доступа к данным.\n", + "\n", + "Также, например, классы хорошо ложатся на реализацию всевозможных игр. Например, давайте подумаем о игре жанра RPG (role-playing game). Там есть квесты, монстры, игроки, предметы инвентаря — все это со своими свойствами и возможностями. Классы позволяют все это реализовать в программном коде. Давайте начнем знакомиться с классами, и прежде всего, откроем интерактивный интерпретатор Python, запустив консоль, набрав Python 3, и посмотрим.\n", + "\n", + "Встроенные типы, о которых мы рассказывали вам на первой неделе, на самом деле являются классами в Python'е. Я набрал `int`, и видим, что это класс `int`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1712fb7d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "int" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "int" + ] + }, + { + "cell_type": "markdown", + "id": "d5cc4612", + "metadata": {}, + "source": [ + "То же самое, например, с классом `float`. На второй неделе мы знакомили вас со структурами данных, такими как, например, `dict` — словарь, или список — `list`. Это тоже классы. Когда мы создаем переменную и присваиваем ей число, например, `13`, мы, на самом деле, создаем объект класса `int` и можем с ним работать.\n", + " \n", + "Посмотрим тип нашей переменной — это как раз класс `int`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "90641e72", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "int" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num = 13\n", + "type(num)" + ] + }, + { + "cell_type": "markdown", + "id": "14509c8d", + "metadata": {}, + "source": [ + "В Python есть встроенная функция `isinstance`. `isintance` — это функция, которая позволяет в рантайме посмотреть, удовлетворяет ли какой-то объект какому-либо классу, типу. Давайте попробуем вызвать эту функцию и проверим, что переменная `num`, которой мы присвоили число 13, является объектом класса `int`. Это `True`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e8a30b65", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(num, int)" + ] + }, + { + "cell_type": "markdown", + "id": "589be9aa", + "metadata": {}, + "source": [ + "Если бы мы попробовали проверить, что это `float`, мы увидели бы, что это `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3ee4dd3a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(num, float)" + ] + }, + { + "cell_type": "markdown", + "id": "74793a85", + "metadata": {}, + "source": [ + "Как мы видим, классы есть и в стандартной библиотеке Python'а. Давайте посмотрим, как реализовывать и писать свои собственные классы.\n", + "\n", + "Чтобы объявить класс, мы используем ключевое слово `class`, за которым следует название класса.\n", + "\n", + "Классы в Python'е принято называть `CamelCase`'ом, то есть с большой буквы. После этого мы ставим двоеточие и дальше идет блок класса, блок пространства имен класса. В данном случае мы используем ключевое слово `pass`. Помните, на первой неделе я говорил, что ключевое слово `pass` может использоваться для определения класса, который ничего не делает? Вот это как раз тот случай. В данном случае мы объявили класс `Human`, который пока ничего не умеет." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7072ec4c", + "metadata": {}, + "outputs": [], + "source": [ + "class Human:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "d383e670", + "metadata": {}, + "source": [ + "Есть другой способ определить пустой класс, который ничего не делает — используя `docstring`. Мы пишем имя класса, и в блоке класса мы просто пишем строку документирования. Это также валидный синтаксис на Python." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b4956140", + "metadata": {}, + "outputs": [], + "source": [ + "class Robot:\n", + " \"\"\"Данный класс позволяет создавать роботов\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "26607708", + "metadata": {}, + "source": [ + "Теперь, если мы посмотрим, что представляет собой объект `Robot`, мы увидим, что это класс \"__main__.Robot\"." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "083bd8f8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(Robot)" + ] + }, + { + "cell_type": "markdown", + "id": "842eb9ef", + "metadata": {}, + "source": [ + "`main` в данном случае — это имя модуля, в котором он определен, мы ничего не импортировали, никаких модулей не создавали, поэтому наш модуль — это модуль `main`. Дальше мы видим названия класса. Если мы посмотрим на то, какие есть методы у этого объекта, только что созданного — у класса `Robot`, — то мы увидим, что их на самом деле много." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "85abcf2a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']\n" + ] + } + ], + "source": [ + "print(dir(Robot))" + ] + }, + { + "cell_type": "markdown", + "id": "a2439475", + "metadata": {}, + "source": [ + "Про некоторые из них мы с вами поговорим, а пока давайте поговорим о том, как создавать экземпляры или, как по-другому говорят, объекты класса. Предположим, что у нас есть класс `Planet` — класс, который описывает какую-либо планету, но этот класс описывает абстрактную планету, то есть он описывает все планеты в целом. Когда мы работаем с планетами, нас будут зачастую интересовать именно конкретные экземпляры планет, то есть не какая-то абстрактная планета, а, например, Земля, Марс и так далее." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "10bfe179", + "metadata": {}, + "outputs": [], + "source": [ + "class Planet:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "86ee64b4", + "metadata": {}, + "source": [ + "Для того, чтобы создать экземпляр класса, мы обращаемся к имени класса как к функции — используем синтаксис круглых скобочек — и получаем объект, либо экземпляр, класса." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "a64bd9a2", + "metadata": {}, + "outputs": [], + "source": [ + "planet = Planet()" + ] + }, + { + "cell_type": "markdown", + "id": "04a94495", + "metadata": {}, + "source": [ + "Это мы можем посмотреть на то, что нам печатает Python, когда мы принтим только что созданную переменную, которая связана с объектом класса." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "19a05a52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<__main__.Planet object at 0x03588418>\n" + ] + } + ], + "source": [ + "print(planet)" + ] + }, + { + "cell_type": "markdown", + "id": "1a9a23fa", + "metadata": {}, + "source": [ + "И мы видим, что это `__main__.Planet object`, то есть это уже объект, а не просто класс, и далее идет адрес, по которому этот объект располагается в памяти." + ] + }, + { + "cell_type": "markdown", + "id": "623bad31", + "metadata": {}, + "source": [ + "С классами можно работать точно так же, как и с другими вещами в Python, так как все в Python есть объект, как вы знаете. Ничто нам не мешает оперировать классами, как любыми другими вещами. Например, давайте посмотрим на слайд, и мы видим, что на этом слайде мы создали список, который называется \"Солнечная система\".\n", + "\n", + "Мы знаем, что в нашей Солнечной системе есть 8 планет. Давайте проитерируемся от 0 до 8 невключительно, и создадим 8 планет, и добавим эти планеты в список. Мы видим, что у нас получилось. Наш финальный список Солнечной системы содержит как раз 8 планет." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "b8c1ada6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[<__main__.Planet object at 0x03588658>, <__main__.Planet object at 0x03588418>, <__main__.Planet object at 0x035886D0>, <__main__.Planet object at 0x035886E8>, <__main__.Planet object at 0x03588700>, <__main__.Planet object at 0x03588718>, <__main__.Planet object at 0x03588730>, <__main__.Planet object at 0x03588748>]\n" + ] + } + ], + "source": [ + "solar_system = []\n", + "for i in range(8):\n", + " planet = Planet()\n", + " solar_system.append(planet)\n", + " \n", + "print(solar_system)" + ] + }, + { + "cell_type": "markdown", + "id": "ff0890b4", + "metadata": {}, + "source": [ + "Что важно отметить? Важно отметить, что экземпляры класса хешируются, то есть экземпляры классов могут быть ключами словаря. На слайде вы видите точно такой же пример, как и предыдущий, за исключением того, что теперь мы в качестве Солнечной системы используем словарь." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "3ed4eb38", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{<__main__.Planet object at 0x03588400>: True, <__main__.Planet object at 0x03588748>: True, <__main__.Planet object at 0x035886E8>: True, <__main__.Planet object at 0x03588700>: True, <__main__.Planet object at 0x03588718>: True, <__main__.Planet object at 0x03588730>: True, <__main__.Planet object at 0x03588628>: True, <__main__.Planet object at 0x03588670>: True}\n" + ] + } + ], + "source": [ + "solar_system = {}\n", + "for i in range(8):\n", + " planet = Planet()\n", + " solar_system[planet] = True\n", + " \n", + "print(solar_system)" + ] + }, + { + "cell_type": "markdown", + "id": "9ddfd1c1", + "metadata": {}, + "source": [ + "Однако не все так хорошо. Наши планеты, которые мы объявили, то есть создали экземпляры класса Planet, они не имеют имени. Это не очень удобно, с этим сложно работать. У каждой планеты должно быть свое имя. Для того чтобы дать планете имя, мы должны переопределить инициализатор класса.\n", + "\n", + "В Python'е у классов существует очень много магических методов. Но самый главный, по сути, магический метод — это метод `__init__` с двумя подчеркиваниями в начале и в конце. Метод `init` позволяет переопределить инициализацию класса. Чтобы вы поняли, давайте посмотрим это на примере." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "5408c883", + "metadata": {}, + "outputs": [], + "source": [ + "class Planet:\n", + " def __init__(self, name):\n", + " self.name = name" + ] + }, + { + "cell_type": "markdown", + "id": "13666ce4", + "metadata": {}, + "source": [ + "Мы переопределили метод `__init__`. Про то, как он представлен, мы скоро с вами поговорим." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "8dac9ceb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Earth\n", + "<__main__.Planet object at 0x03588040>\n" + ] + } + ], + "source": [ + "earth = Planet(\"Earth\")\n", + "print(earth.name)\n", + "print(earth)" + ] + }, + { + "cell_type": "markdown", + "id": "50c798e6", + "metadata": {}, + "source": [ + "Но давайте посмотрим. Мы объявляем переменную `Earth`, которая является экземпляром класса `Planet`. После этого вызывается метод `__init__`. Метод `__init__` вызывается автоматически Python'ом при создании экземпляра. Первым аргументом метод `__init__` принимает ссылку на только что созданный экземпляр класса. Он еще не проинициализирован — как раз метод `__init__` его инициализирует. Также он после `self` принимает те аргументы, которые мы передаем, обращаясь к классу, когда мы создаем экземпляр. То есть вот в данном случае на место `name` подставится строка `Earth`. Внутри инициализатора мы можем по ссылке `self` установить так называемые атрибуты экземпляра. В данном случае мы ставим атрибут экземпляра `name` и присваиваем его аргументу `name`.\n", + "\n", + "Теперь у нас у каждой планеты есть свое имя. Мы можем обратиться к имени планеты, а точнее, к атрибуту экземпляра, используя точку, то есть `имя переменной.name`. И мы видим, что на экран напечатается `Earth`. Также, если мы сейчас посмотрим и напечатаем наш класс, то мы увидим, что на экран выведется вот такое вот описание, то есть это объект класса `Planet`.\n", + "\n", + "А что если мы хотим, чтобы когда мы печатаем нашу планету, Python печатал ее имя? На самом деле, для этого существует еще один магический метод — метод `__str__` с двумя подчеркиваниями в начале и в конце. Он позволяет нам переопределить то, как будет печататься объект. В данном случае мы возвращаем имя нашей планеты и теперь, когда мы печатаем на экран наш экземпляр, используя функцию `print`, на экран выводится имя нашей планеты." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "6f57adcd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Earth\n" + ] + } + ], + "source": [ + "class Planet:\n", + " def __init__(self, name):\n", + " self.name = name\n", + " \n", + " def __str__(self):\n", + " return self.name\n", + " \n", + " \n", + "earth = Planet(\"Earth\")\n", + "print(earth)" + ] + }, + { + "cell_type": "markdown", + "id": "ca8fafa0", + "metadata": {}, + "source": [ + "Вернемся к примеру, где мы создаем Солнечную систему. Теперь мы населяем Солнечную систему планетами со своим именем. Однако, обратите внимание, что, несмотря на то, что мы переопределили метод `__str__`, магические классы, мы внутри списка видим объекты, которые печатаются по-прежнему в том представлении, которое печаталось до этого, до переопределения `__str__`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "51071ecf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[<__main__.Planet object at 0x03588730>, <__main__.Planet object at 0x03588670>, <__main__.Planet object at 0x03588748>, <__main__.Planet object at 0x03588928>, <__main__.Planet object at 0x03588958>, <__main__.Planet object at 0x03588988>, <__main__.Planet object at 0x035889B8>, <__main__.Planet object at 0x035889E8>]\n" + ] + } + ], + "source": [ + "solar_system = []\n", + "planet_names = [\n", + " \"Mercury\", \"Venus\", \"Earth\", \"Mars\",\n", + " \"Jupiter\", \"Saturn\", \"Uranus\", \"Neptune\"\n", + "]\n", + "\n", + "\n", + "for name in planet_names:\n", + " planet = Planet(name)\n", + " solar_system.append(planet)\n", + " \n", + "print(solar_system)" + ] + }, + { + "cell_type": "markdown", + "id": "86c8e29e", + "metadata": {}, + "source": [ + "Дело в том, что в данном случае Python использует другой магический метод, когда мы внутри списка, то есть встроенный, другой магический метод — метод `__repr__` с двумя подчеркиваниями, который мы также имеем возможность переопределить для того, чтобы и внутреннее представление объекта также печаталось правильно в данном случае." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "7332eb54", + "metadata": {}, + "outputs": [], + "source": [ + "class Planet:\n", + " def __init__(self, name):\n", + " self.name = name\n", + " \n", + " def __repr__(self):\n", + " return f\"Planet {self.name}\"" + ] + }, + { + "cell_type": "markdown", + "id": "f0f43524", + "metadata": {}, + "source": [ + "Если мы переопределим `__repr__` и выполним тот же самый пример, то мы уже увидим, что внутри нашего списка планеты названы правильно." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "5f2b26d4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Planet Mercury, Planet Venus, Planet Earth, Planet Mars, Planet Jupiter, Planet Saturn, Planet Uranus, Planet Neptune]\n" + ] + } + ], + "source": [ + "solar_system = []\n", + "planet_names = [\n", + " \"Mercury\", \"Venus\", \"Earth\", \"Mars\",\n", + " \"Jupiter\", \"Saturn\", \"Uranus\", \"Neptune\"\n", + "]\n", + "\n", + "\n", + "for name in planet_names:\n", + " planet = Planet(name)\n", + " solar_system.append(planet)\n", + " \n", + "print(solar_system)" + ] + }, + { + "cell_type": "markdown", + "id": "bd1e1eec", + "metadata": {}, + "source": [ + "Магических методов, на самом деле, существует масса, и мы будем вас знакомить с другими магическими методами. \n", + "\n", + "Например, есть метод `__add__` с двумя подчеркиваниями, который позволяет переопределить, как ведут себя два объекта класса, когда мы их складываем друг с другом, и так далее.\n", + "\n", + "Но многие из них мы даже не сможем осветить в рамках нашего курса, но вы всегда сможете посмотреть это в документации.\n", + "\n", + "Теперь давайте вернемся к атрибутам экземпляра и посмотрим, как с ними можно работать. Когда мы создаем экземпляр класса `Planet`, мы можем обратиться к атрибуту через точку, как я уже показывал. Однако это не все — мы можем в любой момент изменить атрибут экземпляра, присвоив ему другое значение. Например, как здесь на слайде показано, мы присвоили атрибуту экземпляра `name` другое значение `Second Earth`, и оно меняется." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "aaf6b1a2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Planet Mars\n" + ] + } + ], + "source": [ + "mars = Planet(\"Mars\")\n", + "print(mars)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "76ad2d40", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Mars'" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mars.name" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "143730ea", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Second Earth?'" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mars.name = \"Second Earth?\"\n", + "mars.name" + ] + }, + { + "cell_type": "markdown", + "id": "ce6886d1", + "metadata": {}, + "source": [ + "Если мы обратимся к несуществующему атрибуту экземпляра, то вызовется `Exception AttributeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "8c93cba5", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'Planet' object has no attribute 'mass'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mmars\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmass\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m: 'Planet' object has no attribute 'mass'" + ] + } + ], + "source": [ + "mars.mass" + ] + }, + { + "cell_type": "markdown", + "id": "fa124450", + "metadata": {}, + "source": [ + "Про то, как отлавливать такие ошибки, мы с вами будем говорить в этом блоке. Также, если мы удалим какой-нибудь атрибут экземпляра, используя конструкцию `del` — мы можем это сделать, — то, обратившись потом к `mars.name`, мы также получим `AttributeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "93b88088", + "metadata": {}, + "outputs": [], + "source": [ + "del mars.name" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "e4a86397", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'Planet' object has no attribute 'name'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mmars\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m: 'Planet' object has no attribute 'name'" + ] + } + ], + "source": [ + "mars.name" + ] + }, + { + "cell_type": "markdown", + "id": "c4484539", + "metadata": {}, + "source": [ + "Давайте подведем итог. Мы с вами посмотрели, как объявлять классы, объявлять классы, которые ничего по большому счету не делают, то есть это просто объявления в пространстве имен класса, который следует после объявления. Мы научились создавать экземпляры классов, или по-другому, объекты классов. Мы рассмотрели, как инициализировать экземпляр класса, то есть как наделять его конкретными атрибутами, чтобы как-то делать наши объекты уникальными — например, как мы помним, дать уникальное имя каждой из планет. Также мы научились работать с атрибутами экземпляра классов, то есть мы можем создавать их, читать и удалять." + ] + }, + { + "cell_type": "markdown", + "id": "3c640f57", + "metadata": {}, + "source": [ + "Давайте продолжим говорить о классах и экземплярах. И сейчас мы посмотрим на то, что такое атрибуты класса. \n", + "\n", + "Иногда вам нужно создать переменную, которая будет работать в контексте класса, но которая не связана с каждым конкретным экземпляром. То есть, эта переменная относится непосредственно к самому классу, а не к экземпляру. Такая переменная называется атрибутом класса и на слайде вы можете видеть ее." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "0c52ac87", + "metadata": {}, + "outputs": [], + "source": [ + "class Planet:\n", + " count = 0\n", + " \n", + " def __init__(self, name, population=None):\n", + " self.name = name\n", + " self.population = population or []\n", + " Planet.count += 1" + ] + }, + { + "cell_type": "markdown", + "id": "1462d08e", + "metadata": {}, + "source": [ + "Вот этот атрибут класса, атрибут, который мы назвали `сount`. В данном случае мы в методе `__init__` для каждого экземпляра увеличиваем количество планет, которое было создано, обращаясь к атрибуту класса через имя класса.\n", + "\n", + "На практике это может быть иногда полезно. Давайте посмотрим, как это сделать. Мы объявляем две переменных `Earth` и `Mars`. Это два экземпляра класса `Planet`. И после этого, когда мы обращаемся к атрибуту `сount`, несмотря на то, что атрибут `сount` не является атрибутом экземпляра, мы получаем его значение." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "e1746036", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] + } + ], + "source": [ + "earth = Planet(\"Earth\")\n", + "mars = Planet(\"Mars\")\n", + "\n", + "print(Planet.count)" + ] + }, + { + "cell_type": "markdown", + "id": "6493afcc", + "metadata": {}, + "source": [ + "Мы также можем обратиться к атрибуту `сount` через объект экземпляр класса и также получим его значение." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "566c2441", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mars.count" + ] + }, + { + "cell_type": "markdown", + "id": "1b02b524", + "metadata": {}, + "source": [ + "В этот момент Python видит, что внутри экземпляра класса такого атрибута нет, и он проверяет сам класс на наличие атрибута класса и находит его. На самом деле, нужно быть осторожным, когда используешь атрибуты класса. Часто может возникнуть путаница, когда мы хотим обращаться к атрибуту как к атрибуту, привязанному к конкретному экземпляру, а на самом деле это атрибут класса. Но за этим нужно следить. Это достаточно неявно, но это не так сложно.\n", + "\n", + "Деструктор экземпляра класса. Что это такое? Когда счетчик ссылок на экземпляр класса достигает 0, помните, мы уже с вами говорили про сборщик мусора в Python и то, что он использует счетчик ссылок, вызывается метод `__del__` экземпляра. Это также магический метод, который Python нам предоставляет возможность переопределить. В данном случае мы переопределяем этот метод для экземпляра и печатаем на экран слово \"goodbye\"." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "ca3eabaf", + "metadata": {}, + "outputs": [], + "source": [ + "class Human:\n", + " def __del__(self):\n", + " print(\"Goodbye!\")" + ] + }, + { + "cell_type": "markdown", + "id": "2f4f8fc2", + "metadata": {}, + "source": [ + "Если мы создадим экземпляр класса, а затем воспользуемся конструкцией `__del__`, чтобы удалить, в этот момент вызовется как раз этот магический метод `__del__` и напечатается \"goodbye\"." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "4a2284b5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Goodbye!\n" + ] + } + ], + "source": [ + "human = Human()\n", + "# в данном случае деструктор отработает - но все же\n", + "# лучше создать метод и вызывать его явно\n", + "del human" + ] + }, + { + "cell_type": "markdown", + "id": "1c895418", + "metadata": {}, + "source": [ + "Однако, на практике лучше магический метод `__del__` не переопределять, потому что нет гарантии, что, по завершению работы интерпретатора Python, он будет вызван. Лучше явно определить метод, который будет выполнять какие-то действия, которые вам нужны. Какие вообще это могут быть действия? Например, вы можете закрыть файл в методе `__del__` , либо разорвать сетевое соединение. Так вот, эти действия лучше описывать явно в методах экземпляра, о которых мы будем говорить с вами в следующей лекции.\n", + "\n", + "Словарь экземпляра и класса. Давайте посмотрим на пример и увидим, что это знакомый нам класс `Planet`, у которого переопределен метод `__init__`, у которого ставятся атрибуты `name` и `population` (население планеты). А также есть атрибут класса. Если мы посмотрим на свойство `__dict__` у экземпляра класса `Planet`, то мы увидим, что это словарь, у которого есть как раз атрибуты нашего класса `name` и `population`." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "906ab5e6", + "metadata": {}, + "outputs": [], + "source": [ + "class Planet:\n", + " \"\"\"This class describes planets\"\"\"\n", + " count = 1\n", + " def __init__(self, name, population=None):\n", + " self.name = name\n", + " self.population = population or []\n", + " \n", + "planet = Planet(\"Earth\")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "c343d912", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'Earth', 'population': []}" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "planet.__dict__" + ] + }, + { + "cell_type": "markdown", + "id": "ed54e0d8", + "metadata": {}, + "source": [ + "Когда мы обращаемся к каким-то атрибутам, Python, в первую очередь, смотрит в эту структуру данных, в этот словарь и ищет там эти атрибуты. Если мы добавляем атрибут экземпляру нашего класса, в данном случае на слайде мы добавляем массу планете Земля, то в словаре экземпляра класса появляется этот атрибут. Соответственно, в следующий раз когда мы пытаемся получить к нему доступ, Питон находит этот атрибут в этом словаре и выдает нам его значение." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "a41571c0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'Earth', 'population': [], 'mass': 5.97e+24}" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "planet.mass = 5.97e24\n", + "planet.__dict__" + ] + }, + { + "cell_type": "markdown", + "id": "7d8f98c1", + "metadata": {}, + "source": [ + "Если мы посмотрим на атрибут `__dict__` у, непосредственно, класса, а не у экземпляра, то мы увидим, что он также присутствует." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "419f8b35", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "mappingproxy({'__module__': '__main__',\n", + " '__doc__': 'This class describes planets',\n", + " 'count': 1,\n", + " '__init__': ,\n", + " '__dict__': ,\n", + " '__weakref__': })" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Planet.__dict__" + ] + }, + { + "cell_type": "markdown", + "id": "5f9fd4b2", + "metadata": {}, + "source": [ + "И это объект `mapping proxy`, который похож на словарь, у которого также есть множество всевозможных ключей, в том числе, это ключ `__doc__`. Это тот `docstring`, который мы прописали нашему классу. Также, обратите внимание, что именно в словаре самого класса присутствует атрибут класса `сount`.\n", + "\n", + "Если мы обратимся к свойству `__doc__`, например, то это свойство будет найдено в этом словаре и Python вернет нам его значение." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "2fd32d64", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'This class describes planets'" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Planet.__doc__" + ] + }, + { + "cell_type": "markdown", + "id": "c80b7e08", + "metadata": {}, + "source": [ + "Как я уже говорил, мы можем обращаться к свойствам, которые есть в словаре класса и от экземпляра конкретного. То есть, если мы обратимся к свойству `__doc__` конкретного экземпляра `Planet`, который является объектом класса `Planet`, мы также увидим на экране нашу строку." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "534389c5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'This class describes planets'" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "planet.__doc__" + ] + }, + { + "cell_type": "markdown", + "id": "e4334801", + "metadata": {}, + "source": [ + "Если мы посмотрим, какие еще есть методы у экземпляра класса, мы увидим, что их, на самом деле, очень много. Например, есть `__hash__()`. Как я говорил, экземпляры класса хешируемые и могут быть использованы в ключах словаря. А также есть очень много других магических методов, про которые мы с вами еще поговорим." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "471a73e8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'count', 'mass', 'name', 'population']\n" + ] + } + ], + "source": [ + "print(dir(planet))" + ] + }, + { + "cell_type": "markdown", + "id": "3f043b02", + "metadata": {}, + "source": [ + "Также в любой момент мы от экземпляра можем получить класс, к которому он принадлежит, используя свойство `__class__`. И видим, что `planet.__class__` нам возвращает `__main__.planet`, наш класс." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "23751219", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.Planet" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "planet.__class__" + ] + }, + { + "cell_type": "markdown", + "id": "d935d836", + "metadata": {}, + "source": [ + "И последнее, на что мы посмотрим, это на конструктор экземпляра класса. Давайте посмотрим на пример." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "a95f1d97", + "metadata": {}, + "outputs": [], + "source": [ + "class Planet:\n", + " def __new__(cls, *args, **kwargs):\n", + " print(\"__new__ called\")\n", + " obj = super().__new__(cls)\n", + " return obj\n", + " \n", + " def __init__(self, name):\n", + " print(\"__init__ called\")\n", + " self.name = name" + ] + }, + { + "cell_type": "markdown", + "id": "4c4bac50", + "metadata": {}, + "source": [ + "Конструктор экземпляра класса позволяет нам переопределить какие-то действия, которые происходят с экземпляром до его инициализации. На самом деле, на практике это встречается нечасто, но знать об этом нужно. В следующих лекциях я вам покажу пример использования магического метода `__new__`, который как раз является конструктором экземпляра класса, в рамках использования метаклассов. А сейчас давайте просто посмотрим на то, в каком порядке вызывается магический метод `__new__`, который мы сейчас переопределим, и магический метод `__init__`, который инициализирует класс." + ] + }, + { + "cell_type": "markdown", + "id": "0950376b", + "metadata": {}, + "source": [ + "Здесь на примере мы переопределили магический метод `__new__`, который принимает первым аргументом класс, это как раз в нашем случае класс `Planet`. И здесь мы сделали то же самое, что делает `Python`, ничего не изменив, за исключением того, что добавили `print`, что был вызван метод `__new__`. Давайте посмотрим, что значит вот эта строка. Вот эта строка - это как создание нового экземпляра класса, который еще не инициализирован. Метод `super` возвращает родителя нашего класса, в данном случае это `Object`. Это класс, от которого наследуются все пользовательские классы в Python 3. Мы вызываем метод `__new__` класса `Object`. И нам возвращается экземпляр. Этот экземпляр мы просто возвращаем." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "db748979", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "__new__ called\n", + "__init__ called\n" + ] + } + ], + "source": [ + "earth = Planet(\"Earth\")" + ] + }, + { + "cell_type": "markdown", + "id": "68c3d35c", + "metadata": {}, + "source": [ + "Когда мы создаем экземпляр класса `Planet` , мы видим, что сначала вызывается метод `__new__`, а затем вызываем метод `__init__`. То есть, сначала создается экземпляр, а затем он инициализируется. То есть, если переложить это на код, происходит примерно следующее: сначала вызывается метод `__new__`, который получает на вход класс, в данном случае `Planet`. А вторым аргументом то, что мы передаем при создании экземпляра, наши аргументы, в данном случае строка `Earth`. И дальше, если конструктор `__new__` вернул нам правильный класс, если он, соответствуя типу, с помощью функции `isinstance` проверяет Python, то вызывается метод `__init__`. То есть, происходит инициализация нашего объекта, используя те аргументы, которые мы передавали для того, чтобы создать экземпляр." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "ac5ef6fe", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "__new__ called\n", + "__init__ called\n" + ] + } + ], + "source": [ + "planet = Planet.__new__(Planet, \"Earth\")\n", + "\n", + "if isinstance(planet, Planet):\n", + " Planet.__init__(planet, \"Earth\")" + ] + }, + { + "cell_type": "markdown", + "id": "a365bf73", + "metadata": {}, + "source": [ + "На этой лекции мы посмотрели на то, что такое атрибут класса, посмотрели на деструктор и конструктор экземпляра. А также поговорили о поиске атрибутов в словаре экземпляра и класса." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/1. Классы и объекты/Методы.ipynb b/3. Объектно-ориентированное программирование/1. Классы и объекты/Методы.ipynb new file mode 100644 index 0000000..cfecebe --- /dev/null +++ b/3. Объектно-ориентированное программирование/1. Классы и объекты/Методы.ipynb @@ -0,0 +1,770 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "bde8fdc5", + "metadata": {}, + "source": [ + "# Методы #" + ] + }, + { + "cell_type": "markdown", + "id": "53f43d5b", + "metadata": {}, + "source": [ + "Давайте усложним немножко наши примеры и добавим классам методы.\n", + "\n", + "Методы это по большому счёту просто функции, которые действуют в контексте экземпляра класса.\n", + "\n", + "Таким образом так как они действует в контексте экземпляра и получают ссылку на экземпляр класса, они могут менять его состояние, обращаясь к атрибутам экземпляра или делать любую полезную работу, которая нам захочется.\n", + "\n", + "Давайте посмотрим, как создать метод экземпляра и вызвать его. Население планеты нестабильно, люди рождаются и умирают поэтому мы создали класс `Human`, у которого есть два атрибута `name` и `age`, имя и возраст и также у нас есть класс планеты, у которой есть атрибут `name` и также атрибут `population`, который является списком и этот список будет содержать людей, которые есть на планете. Вот здесь мы объявили метод `add_human`, который является методом экземпляра и по большому счёту это просто функция, которая принимает первым аргументом `self`, это ссылка на экземпляр класса, а вторым аргументом всё что мы захотим." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "307c9d5a", + "metadata": {}, + "outputs": [], + "source": [ + "class Human:\n", + " def __init__(self, name, age=0):\n", + " self.name = name\n", + " self.age = age\n", + " \n", + " \n", + "class Planet:\n", + " def __init__(self, name, population=None):\n", + " self.name = name\n", + " self.population = population or []\n", + " \n", + " def add_human(self, human):\n", + " print(f\"Welcome to {self.name}, {human.name}!\")\n", + " self.population.append(human)" + ] + }, + { + "cell_type": "markdown", + "id": "ba1164be", + "metadata": {}, + "source": [ + "И так как это обычная функция, она может принимать сколько угодно аргументов как позиционных, так и именованных. В данном случае она принимает один. Это будет экземпляр класса `Human`. Внутри этого метода мы печатаем на экран приветственное сообщение новому человеку и обновляем атрибут экземпляра `population`. Добавляем туда нового человека.\n", + "\n", + "Также внутри методов, так как это просто функция в контексте экземпляра, мы можем использовать конструкцию `return`, чтобы возвращать из них какие-то значения, но в данном случае нам это не потребовалось. Давайте посмотрим как этим пользоваться, мы создаем экземпляр класса `Planet`, `Mars`, дальше мы создаем экземпляр класса `Human`, даем человеку имя Боб и затем мы у экземпляра класса `Planet Mars` вызываем метод `add_human` и передаем в него аргумент, объект нашего человека." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7a00536d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Welcome to Mars, Bob!\n" + ] + } + ], + "source": [ + "mars = Planet(\"Mars\")\n", + "bob = Human(\"Bob\")\n", + "mars.add_human(bob)" + ] + }, + { + "cell_type": "markdown", + "id": "a25fda62", + "metadata": {}, + "source": [ + "Таким образом, мы обновляем население планеты и если мы посмотрим на то что выглядит `mars.population`, мы видим что действительно население планеты обновилось. Kак раз то, что мы делали внутри метода экземпляра `add_human`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5653db75", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n" + ] + } + ], + "source": [ + "print(len(mars.population))" + ] + }, + { + "cell_type": "markdown", + "id": "67cfdcf4", + "metadata": {}, + "source": [ + "Ничто не мешает нам из методов вызывать другие методы. Давайте посмотрим еще на один пример и убедимся в том, что это действительно возможно, и как это сделать. Мы объявляем класс `Human`, у которого программист предположим решил сделать атрибут `name` и `age` вот такими, то есть назвать их, начиная с символа нижнего подчёркивания. Также у этого класса метод экземпляра `say`, который также начинается с нижнего подчёркивания, а ещё два метода `say_name` и `say_how_old`, которые печатают, сколько человеку лет и какое у него имя." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8793452f", + "metadata": {}, + "outputs": [], + "source": [ + "class Human:\n", + " def __init__(self, name, age=0):\n", + " self._name = name\n", + " self._age = age\n", + " \n", + " def _say(self, text):\n", + " print(text)\n", + " \n", + " def say_name(self):\n", + " self._say(f\"Hello, I am {self._name}\")\n", + " \n", + " def say_how_old(self):\n", + " self._say(f\"I am {self._age} years old\")" + ] + }, + { + "cell_type": "markdown", + "id": "f407522c", + "metadata": {}, + "source": [ + "Что значат эти символы нижнего подчёркивания? В других языках программирования вы могли встречаться с `protected`, `рrivate` атрибутами то есть механизмами защиты атрибутов, которые определены внутри класса. В Python-e такого механизма нет и по большому счёту вы можете достучаться до любого атрибута, однако есть такое соглашение, что если атрибут либо метод названы c символа нижнего подчёркивания, то ими пользоваться не рекомендуется потому, что в дальнейших версиях той или иной библиотеки программист, который пишет может либо отказаться от этих атрибутов или методов, начинающихся с символа нижнего подчеркивания, либо поменять их поведение каким-то образом.\n", + "\n", + "Однако у нас есть класс, у которого есть два публичных метода, `say_name` и `say_how_old`, оба эти методы вызывают приватный метод `say`. Посмотрим как этим пользоваться." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "94893dfc", + "metadata": {}, + "outputs": [], + "source": [ + "bob = Human(\"Bob\", age=29)" + ] + }, + { + "cell_type": "markdown", + "id": "de0e06b9", + "metadata": {}, + "source": [ + "Мы объявляем переменным Боб, который является экземпляром класса `Human` и дальше мы можем вызывать методы, которые внутри себя будут вызывать другой метод, и мы видим что печатается то, что мы хотим." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "57686020", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, I am Bob\n", + "I am 29 years old\n" + ] + } + ], + "source": [ + "bob.say_name()\n", + "bob.say_how_old()" + ] + }, + { + "cell_type": "markdown", + "id": "04f544c3", + "metadata": {}, + "source": [ + "А вот так вот обращаться к атрибуту экземпляра и методу экземпляра не рекомендуется как раз потому что эти атрибуты и метод начинаются с символа нижнего подчёркивания." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "363cb13b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bob\n", + "Whatever we want\n" + ] + } + ], + "source": [ + "# не рекомендуется!\n", + "print(bob._name)\n", + "# не рекомендуется!\n", + "bob._say(\"Whatever we want\")" + ] + }, + { + "cell_type": "markdown", + "id": "02b00ebd", + "metadata": {}, + "source": [ + "Несмотря на то, что Python предоставляет эту возможность. \n", + "\n", + "Другой концепт, который есть в реализации классов на Python, это так называемый метод класса либо `classmethod`. Зачем это может быть нужно? Так может получиться, что вам нужно объявить метод, но этот метод не привязан к конкретному экземпляру, но в тоже время он вовлекает класс в свою работу тем или иным образом, сам класс, то есть сам класс, которым вы оперируете, лучше всего посмотреть на примере." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "78cf5748", + "metadata": {}, + "outputs": [], + "source": [ + "class Event:\n", + " def __init__(self, description, event_date):\n", + " self.description = description\n", + " self.date = event_date\n", + " \n", + " def __str__(self):\n", + " return f\"Event \\\"{self.description}\\\" at {self.date}\"" + ] + }, + { + "cell_type": "markdown", + "id": "901db4da", + "metadata": {}, + "source": [ + "Давайте отвлечемся немножко от планет и людей и посмотрим класс `Event`, класс, который описывает события. У этого класса есть описание и дата, когда это событие происходит, также я переопределил метод `str`, чтобы когда мы печатали `event` на экран выводилось что-то осмысленное.\n", + "\n", + "Посмотрим как им пользоваться. Мы получаем текущую дату используя модуль `datetime` стандартной библиотеки Python, и дальше как обычно создаем экземпляр класса `Event`, инициализируя его с помощью описания и даты." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9a620e3b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Event \"Рассказать, что такое @classmethod\" at 2021-06-08\n" + ] + } + ], + "source": [ + "from datetime import date\n", + "\n", + "event_description = \"Рассказать, что такое @classmethod\"\n", + "event_date = date.today()\n", + "\n", + "event = Event(event_description, event_date)\n", + "print(event)" + ] + }, + { + "cell_type": "markdown", + "id": "8fdf3c22", + "metadata": {}, + "source": [ + "На данном слайде мы добавим нашему классу `Event` метод класса. Зачем?\n", + "\n", + "Давайте рассмотрим такой пример: повсеместно сейчас распространены мессенджеры, в которых есть масса умных помощников, которые предоставляют тот или иной функционал, а пользователи могут писать им текст и получать в ответ какой-то ответ, пользователь пишет умному помощнику \"Привет! Я хочу добавить такое-то событие на такую то дату\", умный помощник принимает этот ввод пользователя, анализирует его на серверной стороне и добавляет событие в календарь пользователя." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1d655d29", + "metadata": {}, + "outputs": [], + "source": [ + "def extract_description(user_string):\n", + " return \"открытие чемпионата мира по футболу\"\n", + "\n", + "def extract_date(user_string):\n", + " return date(2018, 6, 14)\n", + "\n", + "class Event:\n", + " def __init__(self, description, event_date):\n", + " self.description = description\n", + " self.date = event_date\n", + " \n", + " def __str__(self):\n", + " return f\"Event \\\"{self.description}\\\" at {self.date}\"\n", + " \n", + " @classmethod\n", + " def from_string(cls, user_input):\n", + " description = extract_description(user_input)\n", + " date = extract_date(user_input)\n", + " return cls(description, date)" + ] + }, + { + "cell_type": "markdown", + "id": "a590e61d", + "metadata": {}, + "source": [ + "Давайте посмотрим как это можно было бы сделать на Python-е. А у нас есть все тот же класс `Event`, но мы добавили ему метод `from_string`, который обернули декоратором `classmethod`. `Сlassmethod` - это встроенный объект, вам не нужно его ниоткуда импортировать, данный декоратор делает метод методом класса, в отличие от метода экземпляра, метод класса первым аргументом принимает не ссылку на конкретный экземпляр класса, а сам класс непосредственно, то есть в данном случае это будет класс `Event`, а не конкретный экземпляр. Внутри этого метода мы из пользовательского ввода, в данном случае это `user_input`, каким-то образом выделяем дату, которую пользователь хочет создать и описание события, на примере мы сделали это с помощью коротких функций-заглушек, которые из строки выделяют дату и описание, на самом деле - это нетривиальные задачи. Для того чтобы анализировать пользовательский ввод в таком ключе, требуется специальные библиотеки или даже есть для этого сервисы. Это не так просто, но мы для простоты возвращаем просто какое-то описание и какую-то дату. Получив описание и дату мы можем проинициализировать класс и вернуть экземпляр класса события на основе вот той строки, которую нам передал пользователь и получить экземпляр класса `Event` и как-то дальше с ним оперировать, добавить его в календарь.\n", + "\n", + "Таким образом `classmethod` может быть полезен как например альтернативный конструктор вашего класса. Давайте посмотрим, как им пользоваться." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "9c063dcf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Event \"открытие чемпионата мира по футболу\" at 2018-06-14\n" + ] + } + ], + "source": [ + "event = Event.from_string(\n", + " \"добавить в мой календарь открытие чемпионата мира по футболу на 14 июня 2018 года\"\n", + ")\n", + "\n", + "print(event)" + ] + }, + { + "cell_type": "markdown", + "id": "1ebd41ff", + "metadata": {}, + "source": [ + "Когда после того как мы всё это объявили, у нас есть класс `Event` и мы можем вызвать метод класса `from_string` и передать в него строку. Произойдёт анализ этой строки и в результате нам вернётся экземпляр класса `Event` и мы видим, что у нас всё получилось, на экран вывелось как раз правильное описание события.\n", + "\n", + "Возможно вам сейчас не очень очевидно, зачем нужны класс-методы, но это становится более очевидным, когда появляется наследование. Класс-метод принимает на вход класс и этот класс будет всегда тем, который, внутри которого этот класс-метод описан и вы относительно этого класса можете не только его как-то инициализировать и вернуть, но вы также можете обращаться к атрибутам класса, делать всё что угодно, что вы можете сделать с классом. Внутри стандартной библиотеки класс-методы тоже активно используются. И например, вы знаете, что `dict` - это класс. И соответственно у `dict`а, у словаря, есть метод `fromkeys`. Это как раз метод класса, который принимая какой-то итерабельный объект, возвращает нам проинициализированный словарь." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "06466ac7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'1': None, '2': None, '3': None, '4': None, '5': None}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dict.fromkeys(\"12345\")" + ] + }, + { + "cell_type": "markdown", + "id": "f2091ae0", + "metadata": {}, + "source": [ + "В данном случае мы передали в него строку и получили словарь, где ключ - это элeменты последовательности в строке.\n", + "\n", + "Мы научились объявлять и работать с методами экземпляров, а также посмотрели на методы класса, которые в отличие от методов экземпляров принимают первым аргументом не ссылку на конкретный экземпляр класса, а сам класс непосредственно. \n", + "\n", + "Далее мы посмотрим на другие особенности реализации классов в Python-e и посмотрим на статический метод и на `property`." + ] + }, + { + "cell_type": "markdown", + "id": "7b3ebb12", + "metadata": {}, + "source": [ + "Следующим концептом, на который мы посмотрим, является статический метод, либо `staticmethod`. Давайте посмотрим на примере." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d91edf92", + "metadata": {}, + "outputs": [], + "source": [ + "class Human:\n", + " def __init__(self, name, age=0):\n", + " self.name = name\n", + " self.age = age\n", + " \n", + " @staticmethod\n", + " def is_age_valid(age):\n", + " return 0 < age < 150" + ] + }, + { + "cell_type": "markdown", + "id": "848dda48", + "metadata": {}, + "source": [ + "Может так получиться, что вам нужно объявить метод в контексте класса, но этот метод не оперирует ни ссылкой на конкретный экземпляр класса, ни самим классом непосредственно, как мы видели в методе класса. В таком случае вам может помочь статический метод. Зачем вам может понадобиться статический метод? На самом деле, статический метод — это просто вопрос организации кода, то есть вы его используете тогда, когда вам необходимо, чтобы к нему обращались относительно имени класса либо экземпляра. Давайте посмотрим на примере, как объявить статический метод. Мы объявляем `class Human`, у которого есть имя и возраст, и объявили статический метод `is_age_valid`, который проверяет возраст на соответствие каким-то возрастным ограничениям.\n", + "\n", + "Чтобы объявить статический метод, мы воспользовались декоратором `staticmethod`, опять же, это встроенный объект в Python'е, не нужно его ниоткуда импортировать. После того как мы обернули функцию декоратором `staticmethod`, мы получаем статический метод. Статический метод принимает только те аргументы, которые ему передают. Обратите внимание, что здесь нет ни `self`, ни `class` аргументов. Обращаться к нему можно вот так, то есть относительно имени класса самого `Human`, либо относительно экземпляра. И в том, и в другом случае происходит одно и то же, и код отработает." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a433bb69", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# можно обращаться от имени класса\n", + "Human.is_age_valid(35)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e8b724cd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# или от экземпляра:\n", + "human = Human(\"Old Bobby\")\n", + "human.is_age_valid(234)" + ] + }, + { + "cell_type": "markdown", + "id": "f084a559", + "metadata": {}, + "source": [ + "Опять же, подчеркну, что в данном случае мы могли бы эту функцию объявить просто как обычную функцию, вне контекста класса, вне пространства имен класса. Но мы решили сделать это так, просто из соображений того, что этим будет удобнее пользоваться." + ] + }, + { + "cell_type": "markdown", + "id": "1c9f53ed", + "metadata": {}, + "source": [ + "Далее, еще один мощный концепт, который есть в языке Python и встречается в стандартной библиотеке и в библиотеках повсеместно, является `property`. `Property`, или по-другому вычисляемые свойства. Зачем они нужны? `Property` позволяют изменять поведение и выполнять какую-то вычислительную работу при обращении к атрибуту экземпляра, либо при изменении атрибута, либо при его удалении. Проще всего это понять на примере. Давайте начнем немножко издалека. Предположим, у нас есть класс `Robot`. У этого класса есть переопределенный метод `init`, который инициализирует экземпляр класса одним атрибутом `power` — это мощность робота." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "9c523914", + "metadata": {}, + "outputs": [], + "source": [ + "class Robot:\n", + " def __init__(self, power):\n", + " self.power = power" + ] + }, + { + "cell_type": "markdown", + "id": "39bdcadc", + "metadata": {}, + "source": [ + "Соответственно, пользоваться всем этим можно вот так, ничего нового для вас здесь нет." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "bc8b04f7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "200\n" + ] + } + ], + "source": [ + "wall_e = Robot(100)\n", + "wall_e.power = 200\n", + "print(wall_e.power)" + ] + }, + { + "cell_type": "markdown", + "id": "4e298ac2", + "metadata": {}, + "source": [ + "Мы инициализируем экземпляр и далее можем в любой момент поменять мощность робота и установить ему новое значение." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "430005b5", + "metadata": {}, + "outputs": [], + "source": [ + "wall_e.power = -20" + ] + }, + { + "cell_type": "markdown", + "id": "5d666cb5", + "metadata": {}, + "source": [ + "Предположим, что так получилось, что вы заметили, что другие программисты, которые пользуются вашим классом `Robot`, иногда ставят ему отрицательную мощность, а это невалидное значение, и вам хотелось бы, чтобы когда другие программисты ставят отрицательную мощность, эта мощность на самом деле ставилась бы в ноль. Как можно поступить в таком случае? Первое, что приходит на ум, это отрефакторить наш класс и добавить метод экземпляра `set_power`, который, принимая мощность, будет проверять, что мощность меньше нуля и в таком случае ставить ее в ноль, либо ставить в то значение, которое нам передали." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "c1379a30", + "metadata": {}, + "outputs": [], + "source": [ + "class Robot:\n", + " def __init__(self, power):\n", + " self.power = power\n", + " \n", + " def set_power(self, power):\n", + " if power < 0:\n", + " self.power = 0\n", + " else:\n", + " self.power = power" + ] + }, + { + "cell_type": "markdown", + "id": "93e760ed", + "metadata": {}, + "source": [ + "Таким образом, пользоваться этим можно уже вот так. Мы инициализируем объект `Robot` и дальше вызываем у него метод `set_power`, который в случае отрицательной мощности поставит ее в ноль, и мы видим, что у нас получилось." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "cfbadc0b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "source": [ + "wall_e = Robot(100)\n", + "wall_e.set_power(-20)\n", + "print(wall_e.power)" + ] + }, + { + "cell_type": "markdown", + "id": "192fed6c", + "metadata": {}, + "source": [ + "Однако здесь есть небольшой нюанс. В таком случае программисту, который использует ваш класс, придется менять свой код. То есть это потребует не только рефакторинга от вас, но и рефакторинга от других программистов. Есть ли способ проще? Есть." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "4cac7f06", + "metadata": {}, + "outputs": [], + "source": [ + "class Robot:\n", + " def __init__(self, power):\n", + " self._power = power\n", + " \n", + " power = property()\n", + " \n", + " @power.setter\n", + " def power(self, value):\n", + " if value < 0:\n", + " self._power = 0\n", + " else:\n", + " self._power = value\n", + " \n", + " @power.getter\n", + " def power(self):\n", + " return self._power\n", + " \n", + " @power.deleter\n", + " def power(self):\n", + " print(\"make robot useless\")\n", + " del self._power" + ] + }, + { + "cell_type": "markdown", + "id": "11ac651e", + "metadata": {}, + "source": [ + "Как раз здесь и появляется `property`. Давайте посмотрим, что это. В данном случае это тот же самый класс `Robot`, у которого `power` теперь является объектом `property`. Опять же, это встроенный объект, ниоткуда не нужно его импортировать. У этого `property` есть на самом деле три метода — это метод `getter`, метод `setter`, и метод `deleter`. Все эти три метода мы можем переопределить, чтобы менять поведение и выполнять какую-то полезную работу при обращении к атрибуту, при присваивании атрибуту какого-то нового значения либо при удалении атрибута. \n", + "\n", + "Давайте посмотрим, как это сделано. Мы объявили `class Robot`, и теперь `_power` у него является приватным атрибутом. А `power` без нижнего подчеркивания теперь является объектом `property`. Вот таким образом он инициализируется. Далее мы можем объявить три метода и обернуть их декораторами. `power.setter` — это тот метод, который будет выполняться, когда мы будем менять атрибут экземпляра `power`. `power.getter` — это тот метод, который будет выполняться, когда мы будем читать атрибут `power`. `power.deleter` — это тот метод, соответственно, который будет выполняться при удалении атрибута.\n", + "\n", + "Внутри этих методов мы можем выполнять какую-то полезную работу. Например, в методе `setter`, который принимает значение, которое пользователь пытается присвоить атрибуту, мы проверяем, что это значение меньше нуля, и, соответственно, ставим приватный атрибут power в ноль. То же самое, что мы делали в методе `set_power` чуть ранее. \n", + "\n", + "Соответственно, в методе `getter` объекта `property` мы возвращаем значение приватного атрибута `power`. Теперь, когда другой программист будет пользоваться вашим классом, он может по прежнему использовать старый подход и ставить атрибут экземпляру. Даже если он поставит отрицательный атрибут, этот атрибут будет выставлен в ноль в итоге, потому что отработает `setter` метод нашего свойства, нашего `property`.\n", + "\n", + "Если мы попробуем удалить этот атрибут, то выполнится `deleter` метод, который мы также переопределили, и, соответственно, удалит атрибут `power` из нашего экземпляра, тем самым сделав робота бесполезным." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "cdf26fee", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "source": [ + "wall_e = Robot(100)\n", + "wall_e.power = -20\n", + "print(wall_e.power)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "ace80de7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "make robot useless\n" + ] + } + ], + "source": [ + "del wall_e.power" + ] + }, + { + "cell_type": "markdown", + "id": "1adc41dc", + "metadata": {}, + "source": [ + "Иногда нужно как-то модифицировать чтение атрибута и выполнять какую-то полезную работу при чтении, и это единственное, что вам требуется. То есть не нужно менять поведение при изменении значения атрибута либо при его удалении. В таком случае есть более короткая запись." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "5b4c26cb", + "metadata": {}, + "outputs": [], + "source": [ + "class Robot:\n", + " def __init__(self, power):\n", + " self._power = power\n", + " \n", + " @property\n", + " def power(self):\n", + " # здесь могут быть любые полезные вычисления\n", + " return self._power" + ] + }, + { + "cell_type": "markdown", + "id": "1be7bf60", + "metadata": {}, + "source": [ + "Мы можем объявить метод, обернуть его декоратором `property` без всяких суффиксов `getter`, `setter` и `deleter`, и это будет вычисляемым свойством класса, к которому можно обращаться вот так, как вы видите на слайде, то есть просто `power`. Удобно." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "09978b6e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "200" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wall_e = Robot(200)\n", + "wall_e.power" + ] + }, + { + "cell_type": "markdown", + "id": "d4425bc7", + "metadata": {}, + "source": [ + "На этой лекции мы рассмотрели, что такое статический метод. То есть в отличие от обычного метода экземпляра, который на вход принимает ссылку на конкретный экземпляр, и методы класса, который на вход принимает `class`, с которым идет работа, статический метод не принимает ничего, только те аргументы, которые в него передают. Используется он для того, когда нам из соображений удобства удобно обращаться к функции в контексте класса. Также мы посмотрели на такой концепт, как `property`, то есть вычисляемое свойство." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/1. Классы и объекты/Пример на классы.ipynb b/3. Объектно-ориентированное программирование/1. Классы и объекты/Пример на классы.ipynb new file mode 100644 index 0000000..7951a07 --- /dev/null +++ b/3. Объектно-ориентированное программирование/1. Классы и объекты/Пример на классы.ipynb @@ -0,0 +1,368 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7749287c", + "metadata": {}, + "source": [ + "# Пример на классы #" + ] + }, + { + "cell_type": "markdown", + "id": "3efae2d7", + "metadata": {}, + "source": [ + "Теперь от теории классов давайте перейдем к практике.\n", + "\n", + "Мы напишем небольшую программку, причем напишем её с нуля, с самого начала и эта программа будет содержать класс.\n", + "\n", + "Класс будет принимать на вход название города и будет иметь метод, который будет возвращать прогноз погоды в этом городе.\n", + "\n", + "На самом деле этот класс можно будет расширять бесконечно, например добавляя ему методы для получения грядущих событий в вашем любимом городе либо новостей относящихся к нему.\n", + "\n", + "Давайте начнём." + ] + }, + { + "cell_type": "markdown", + "id": "69e00a67", + "metadata": {}, + "source": [ + "Первое что мы сделаем, это создадим директорию в которой будем работать, перейдем в неё, создадим virtual env - вы уже умеете это делать. После того, как виртуальное окружение создастся мы должны его активировать. Нам понадобятся две сторонние библиотеки для работы нашей программы. Давайте их установим. Первая из них это библиотечка `requests`, а вторая это `python-dateutil`. `Request` нам потребуется чтобы делать `http` запросы, а `Python-dateutil` позволит преобразовывать даты, которые представлены в виде строки в Python'овское представление, то есть, в объекты модуля `datetime`. У нас установились наши библиотеки." + ] + }, + { + "cell_type": "markdown", + "id": "2c37bb80", + "metadata": {}, + "source": [ + " Сейчас я начну программировать в среде программирования `Visual Studio Code` или по другому говоря `VSCode`. Это новая для вас среда, возможно многие из вас с ней не знакомы. Вот собственно сейчас мы с ней и познакомимся. \n", + "\n", + "\n", + "Внутри `VSCode`'а мы откроем нашу директорию в которой мы только что создали виртуальное окружение. Это делается с помощью `file - open`. Открываем нашу директорию и вот она открылась. Мы видим наше виртуальное окружение. Следующим шагом мы должны указать, где же находится интерпретатор Python для нашего проекта. `Select wordspace interpreter`. Выбираем интерпретатор Python из нашего виртуального окружения. Внутри нашего проекта мы можем создать файлик. Назовем его `city.py`. Напомню что наша программка будет выдавать прогноз погоды в городе. Как только мы создали файл, `Visual Studio Code` предлагает нам установить `linter`, то есть специальные утилиты, которые помогают нам программировать. В данном случае мы установим `linter pep8` чтобы он следил за стилем нашего кода, а также `linter flake8`. Он будет помогать нам находить очевидные ошибки в нашем коде. `Pylint` мы не будем устанавливать, но это также замечательный `linter`. Теперь мы наконец-то можем программировать. С чего же нам начать? Давайте начнем со стандартного шага. Напишем `if __name__ = \"__main__\"`, то есть будем запускать нашу программу только тогда, когда она вызывается напрямую. А не импортируется. Внутри этой конструкции вызовем функцию `_main`. Назовем её начиная с символа нижнего подчеркивания, чтобы подчеркнуть то, что она является приватной и не должна использоватся из стороннего кода. Объявим эту функцию. Нам нужен класс, который будет принимать на вход имя города. Давайте начнем программу писать с конца. То есть не имея ещё класса объявим скелет нашей программы. Будем смотреть погоду в Москве." + ] + }, + { + "cell_type": "markdown", + "id": "c918e4d0", + "metadata": {}, + "source": [ + "У экземпляра класса `CityInfo` будет метод `weather_forecast`. Этот метод вернет нам список с прогнозом погоды на несколько дней вперед и последнее, что мы сделаем, это напечатаем его. Причем напечатаем красиво и будем для этого использовать модуль стандартной библиотеки `pprint` - это `pretty print`. Чтобы это заработало, нам конечно же нужно импортировать модуль `pretty print`. Теперь у нас есть код, который мы должны реализовать." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "791cc1a5", + "metadata": {}, + "outputs": [], + "source": [ + "import pprint\n", + "\n", + "def _main():\n", + " city = CityInfo(sys.argv[1])\n", + " forecast = city.weather_forecast()\n", + " pprint.pprint(forecast)\n", + " \n", + "if __name__ == \"__main__\":\n", + " _main()" + ] + }, + { + "cell_type": "markdown", + "id": "22e87f7b", + "metadata": {}, + "source": [ + "`Flake8` подчеркивает нам то, что у нас ещё нет класса `CityInfo`. Давайте его создадим. `Class CityInfo`. У него будет переопределен метод `__init__`, то есть иницализатор класса и он будет принимать название города. Устанавливаем атрибут экземпляра. И также у этого класса должен быть метод `weather_forecast`, который должен возвращать прогноз погоды. Пока он ничего не будет делать." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "da8d8628", + "metadata": {}, + "outputs": [], + "source": [ + "class CityInfo:\n", + " def __init__(self, city):\n", + " self.city = city\n", + " \n", + " def weather_forecast(self):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "96ccaa75", + "metadata": {}, + "source": [ + "Давайте подумаем о том, откуда нам брать прогноз погоды. В интернете есть замечательный ресурс `yahoo weather api`, представляемый компанией `Yahoo`. Мы будем пользоваться им. Перейдем на их сайт и посмотрим как нам получить прогноз погоды. Вот в этой формочке мы можем поэкспериментировать с их `api`. На самом деле тут уже практически готов правильный запрос. Нужно только в одном месте поменять название места, где мы хотим смотреть прогноз погоды, на наш и указать, что результат мы хотим получать не в Фаренгейтах, а в Цельсиях, температуру. Я заранее посмотрел, как это делается, конечно же и мы можем нажать кнопочку тест и `yahoo` предоставляет нам ссылку, по которой мы можем перейти и увидеть прогноз погоды в Москве. На самом деле это большой `json`, внутри которого есть много вложенных полей. И прогноз погоды скрыт достаточно глубоко. Вот он. Мы видим, что здесь несколько объектов внутри списка по дням. Здесь есть, например, сегодняшняя дата и так далее.\n", + "\n", + "Наша цель - получить эти данные. Однако мы не будем просто так писать код, делающий http-запрос внутри метода `weather_forecast`. Мы создадим специальный класс, который будет называтся `YahooWeatherForecast`. У него будет метод `get`, который будет принимать город. И вот именно этот класс будет ответственный за то, чтобы ходить по сети и делать запросы к `yahoo`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "31b8e09c", + "metadata": {}, + "outputs": [], + "source": [ + "class YahooWeatherForecast:\n", + " def get(self, city):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "2cb6eb49", + "metadata": {}, + "source": [ + "На самом деле чем это хорошо? А тем что мы этот класс впоследствии сможем подменить другим. Если вдруг с `API Yahoo` что-то случится, у нас всегда будет возможность поменять только один компонент нашей программы, только один класс заменить и весь остальной код программы продолжит работать. Давайте сделаем http-запрос. У нас в буфере обмена содержится тот url-адрес, который `Yahoo` нам предоставил. Это достаточно длинная строка и нам нужно в ней заменить название города `moscow` на то что мы получаем в аргументах, на наш `city`. По-хорошему эту строку желательно разбить на несколько, чтобы `pep8 linter` не ругался, но так как это пример, мы этого сейчас делать не будем, но на практике это нужно сделать.\n", + "\n", + "Теперь мы можем воспользоватся библиотечкой `requests`. Давайте сначала её импортируем. И мы можем получить данные. Делаем `requests.get(url).json` То есть преобразовываем ответ `json`'а в Python'овское представление. В данном случае это будет словарь. \n", + "\n", + "Теперь нам нужно достучаться до того вложенного объекта, который был в этих данных. Давайте это сделаем. Напишем, зададим переменную `forecast_data`, которая будет равнятся следующему." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f8bf861e", + "metadata": {}, + "outputs": [], + "source": [ + "class YahooWeatherForecast:\n", + " def get(self, city):\n", + " url = f\"http://query.yahooapis.com/city/{city}\"\n", + " data = requests.get(url).json()\n", + " \n", + " forecast_data = data[\"query\"][\"forecast\"]" + ] + }, + { + "cell_type": "markdown", + "id": "2dce3928", + "metadata": {}, + "source": [ + "Смотрите, у нас внутри этих данных на верхнем уровне находится словарь `query`. Обращаемся к нему. Дальше внутри `query` у нас список `forecast`. Список с прогнозом погоды на несколько дней. Здесь дата представлена как строка и также есть самая максимальная температура за день, она доступна по ключу `high`, мы будем брать дату и вот эту температуру самую максимальную. \n", + "\n", + "Определим список словарей `forecast` и заполним его данными. Для этого мы проитерируемся по `forecast_data`, полученным с Yahoo и заполним `forecast` данными, пригодными для нас.\n", + "\n", + "Возьмем дату оттуда, а также возьмем оттуда самую максимальную температуру за день. Что еще хотелось бы здесь сразу сделать, мы видим, что дата здесь представлена как строка. Как раз для этого нам пригодится модуль `dateutil`, который мы устанавливали в самом начале, в нем содержится полезная функция `parse`, внутри модуля `dateutil.parser`, внутри пакета и мы можем ее импортировать и с помощью ее преобразовать строку, содержащую дату в объект даты из модуля `datetime` стандартной библиотеки Python. И в принципе все, мы можем сделать `return forecast` и сохранить." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5e39fe6e", + "metadata": {}, + "outputs": [], + "source": [ + "from dateutil.parser import parse\n", + "\n", + "class YahooWeatherForecast:\n", + " def get(self, city):\n", + " url = f\"http://query.yahooapis.com/city/{city}\"\n", + " data = requests.get(url).json()\n", + " \n", + " forecast = []\n", + " forecast_data = data[\"query\"][\"forecast\"]\n", + " \n", + " for day_data in forecast_data:\n", + " forecast.append({\n", + " \"date\": parse(day_data[\"date\"]),\n", + " \"high_temp\": int(day_data[\"high\"])\n", + " })\n", + "\n", + " return forecast" + ] + }, + { + "cell_type": "markdown", + "id": "8cfbb47a", + "metadata": {}, + "source": [ + "Теперь мы вернемся к методу экземпляра `weather_forecast` и в этом методе мы обратимся к `YahooWeatherForecast` классу и вызовем его метод `get` для нужного города. Давайте сделаем, расширим метод `__init__`. Он ну нас будет принимать дополнительный параметр необязательный `forecast_provider` и ставить приватную переменную `_forecast_provider` экземпляру класса. Если же мы `forecast_provider` не передали, будет по умолчанию использоваться `YahooWeatherForecast` экземпляр." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "5b81e29b", + "metadata": {}, + "outputs": [], + "source": [ + "class CityInfo:\n", + " def __init__(self, city, forecast_provider=None):\n", + " self.city = city.lower()\n", + " self._forecast_provider = forecast_provider or YahooWeatherForecast()" + ] + }, + { + "cell_type": "markdown", + "id": "82101659", + "metadata": {}, + "source": [ + "Теперь мы внутри метода `weather_forecast` можем вызвать `self._forecast_provider`, то есть обратиться к приватному атрибуту и вызвать у него метод `get`, передав ему `self.city`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "db98eeda", + "metadata": {}, + "outputs": [], + "source": [ + "class CityInfo:\n", + " def __init__(self, city, forecast_provider=None):\n", + " self.city = city.lower()\n", + " self._forecast_provider = forecast_provider or YahooWeatherForecast()\n", + " \n", + " def weather_forecast(self):\n", + " return self._forecast_provider.get(self.city)" + ] + }, + { + "cell_type": "markdown", + "id": "08480c53", + "metadata": {}, + "source": [ + "На этом по идее у нас программа должна начать работать, давайте попробуем запустить ее. Перейдем в консоль. Мы видим, что мы получили прогноз погоды в Москве. Как мы можем улучшить наш код. Он уже сейчас работает, он достаточно расширяемый, но мы можем сделать еще лучше." + ] + }, + { + "cell_type": "markdown", + "id": "6d200fe3", + "metadata": {}, + "source": [ + "Давайте представим, что нашим приложением, которое мы пишем, пользуется достаточно много пользователей, и на самом деле приходит множество запросов о том, чтобы посмотреть прогноз погоды в Москве. Давайте это сэмулируем. Например, пусть пришло 5 запросов. В этот момент, на каждый из 5 запросов мы будем отсылать `http` запрос к `Yahoo`, для того, чтобы получить прогноз. Это не очень хорошо, потому что каждый `http` запрос - достаточно долгая операция, давайте посмотрим, как мы можем сделать лучше.\n", + "\n", + "Мы можем расширить класс `YahooWeatherForecast` и добавить к нему метод `__init__`. И создать внутри экземпляра приватных переменных, которые будут содержать в себе кэш данных по городам. \n", + "\n", + "Кэш представляет собой словарь, который содержит прогноз погоды в определенном городе, каждый раз, когда будет вызываться метод `get` нашего класса `YahooWeatherForecast`, мы будем проверять, что если город находится в словаре, то мы будем возвращать данные, которые принадлежат этому городу из этого `city` кэша. И каждый раз, когда мы получили данные, мы будем обновлять приватный словарик, приватный атрибут и ставить городу определенный прогноз погоды." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "472c7365", + "metadata": {}, + "outputs": [], + "source": [ + "class YahooWeatherForecast:\n", + " def __init__(self):\n", + " self._city_cache = {}\n", + " \n", + " def get(self, city):\n", + " if city in self._cached_data:\n", + " return self._cached_data[city]\n", + " \n", + " url = f\"http://query.yahooapis.com/city/{city}\"\n", + " \n", + " print(\"sending http request\")\n", + " data = requests.get(url).json()\n", + " \n", + " forecast = []\n", + " forecast_data = data[\"query\"][\"forecast\"]\n", + " \n", + " for day_data in forecast_data:\n", + " forecast.append({\n", + " \"date\": parse(day_data[\"date\"]),\n", + " \"high_temp\": int(day_data[\"high\"])\n", + " })\n", + "\n", + " return forecast" + ] + }, + { + "cell_type": "markdown", + "id": "55a4c91c", + "metadata": {}, + "source": [ + "Теперь, перед тем, как отправить `http` запрос, давайте напишем `print(\"sending http request\")`." + ] + }, + { + "cell_type": "markdown", + "id": "5e6c0394", + "metadata": {}, + "source": [ + "`sending http request` напечаталось только один раз. Это во-первых и быстрее работает в итоге и мы экономим на `http` запросах, например, это может быть полезно так как api-шки сторонние часто бывают платными.\n", + "\n", + "В данном примере мы применили композицию классов, то есть внутри класса `CityInfo` у нас атрибуту присвоен другой класс, часто это очень хороший подход к написанию кода. В данном примере мы не обрабатывали исключения, какие здесь они могли быть. Во-первых сайт `Yahoo` мог быть недоступен, во-вторых, каждый раз, когда мы обращались к ключу словаря мог произойти `key error`, то есть ключа такого могло не произойти, потому что `Yahoo` вернул нам невалидные данные. Также когда мы преобразовывали дату из строки в объект `datetime`, также мог произойти `exception`. Вы пока обрабатывать `exception` не умеете, но вам осталось узнать о классах совсем немножко про наследование и после этого мы научим вас обрабатывать исключения в программах. В данном случае мы написали класс, который умеет получать информацию о городе, причем этот класс достаточно поддерживаемый и его компоненты легко заменяемы. И также он расширяемый." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "f6663800", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2021-01-06 00:00:00\n", + "12\n", + "2021-02-06 00:00:00\n", + "18\n", + "2021-03-06 00:00:00\n", + "12\n", + "2021-04-06 00:00:00\n", + "17\n", + "2021-05-06 00:00:00\n", + "14\n", + "2021-06-06 00:00:00\n", + "14\n", + "2021-07-06 00:00:00\n", + "14\n", + "2021-08-06 00:00:00\n", + "13\n", + "2021-09-06 00:00:00\n", + "18\n" + ] + } + ], + "source": [ + "from random import randint\n", + "from dateutil.parser import parse\n", + "\n", + "\n", + "forecast_data = [\n", + " {\n", + " \"date\": f\"0{i}/06/2021\",\n", + " \"high\": randint(12, 18)\n", + " } for i in range(1, 10)\n", + "]\n", + "\n", + "\n", + "for day_data in forecast_data:\n", + " print(parse(day_data[\"date\"]))\n", + " print(int(day_data[\"high\"]))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/1. Классы и объекты/Тест по классам и объектам (Clear).ipynb b/3. Объектно-ориентированное программирование/1. Классы и объекты/Тест по классам и объектам (Clear).ipynb new file mode 100644 index 0000000..e9b1ca4 --- /dev/null +++ b/3. Объектно-ориентированное программирование/1. Классы и объекты/Тест по классам и объектам (Clear).ipynb @@ -0,0 +1,142 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "71482a2a", + "metadata": {}, + "source": [ + "# Тест по классам и объектам #" + ] + }, + { + "cell_type": "markdown", + "id": "a9a9349c", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "1ae3f26a", + "metadata": {}, + "source": [ + "##### 1. Как узнать тип объекта `obj`?\n", + "\n", + "- [ ] `isinstance(obj)`\n", + "- [ ] `type(obj)`" + ] + }, + { + "cell_type": "markdown", + "id": "f185dff8", + "metadata": {}, + "source": [ + "##### 2. Отметить что является классом:\n", + "\n", + "- [ ] `\"1024\"`\n", + "- [ ] `str`\n", + "- [ ] `1024`\n", + "- [ ] `class`\n", + "- [ ] `int`" + ] + }, + { + "cell_type": "markdown", + "id": "5eec52e6", + "metadata": {}, + "source": [ + "##### 3. Куда записываются атрибуты объекта?\n", + "\n", + "- [ ] `obj.__class__`\n", + "- [ ] `obj.__attrt__`\n", + "- [ ] `obj.__doc__`\n", + "- [ ] `obj.__dict__`" + ] + }, + { + "cell_type": "markdown", + "id": "e67e6935", + "metadata": {}, + "source": [ + "##### 4. Когда вызывается метод `__init__`?\n", + "\n", + "- [ ] При обращении к методу экземпляра\n", + "- [ ] При объявлении класса\n", + "- [ ] При создании экземпляра" + ] + }, + { + "cell_type": "markdown", + "id": "5e090536", + "metadata": {}, + "source": [ + "##### 5. Экземпляры классов хешируются?\n", + "\n", + "- [ ] Да\n", + "- [ ] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "e425d1b2", + "metadata": {}, + "source": [ + "##### 6. Отметьте верные утверждения про `classmethod`\n", + "\n", + "- [ ] Метод первым аргументом принимает класс\n", + "- [ ] Метод не принимает дополнительных аргументов кроме указанных программистом\n", + "- [ ] К этому методу можно обращаться от экземпляра класса\n", + "- [ ] К этому методу можно обращаться от имени класса\n", + "- [ ] Метод первым аргументом принимает ссылку на экземпляр класса\n" + ] + }, + { + "cell_type": "markdown", + "id": "d11a3139", + "metadata": {}, + "source": [ + "##### 7. Отметьте верные утверждения про `staticmethod`\n", + "\n", + "- [ ] К этому методу можно обращаться от экземпляра класса\n", + "- [ ] Метод первым аргументом принимает ссылку на экземпляр класса\n", + "- [ ] Метод первым аргументом принимает класс\n", + "- [ ] Метод не принимает дополнительных аргументов кроме указанных программистом\n", + "- [ ] К этому методу можно обращаться от имени класса" + ] + }, + { + "cell_type": "markdown", + "id": "782a6823", + "metadata": {}, + "source": [ + "##### 8. Для чего используются `@property`?\n", + "\n", + "- [ ] Чтобы создать вычисляемый атрибут\n", + "- [ ] Чтобы делать атрибуты приватными" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/1. Классы и объекты/Тест по классам и объектам.ipynb b/3. Объектно-ориентированное программирование/1. Классы и объекты/Тест по классам и объектам.ipynb new file mode 100644 index 0000000..0f571cf --- /dev/null +++ b/3. Объектно-ориентированное программирование/1. Классы и объекты/Тест по классам и объектам.ipynb @@ -0,0 +1,133 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "71482a2a", + "metadata": {}, + "source": [ + "# Тест по классам и объектам #" + ] + }, + { + "cell_type": "markdown", + "id": "c37061e7", + "metadata": {}, + "source": [ + "1. Как узнать тип объекта `obj`?\n", + "\n", + "- [ ] `isinstance(obj)`\n", + "- [x] `type(obj)`" + ] + }, + { + "cell_type": "markdown", + "id": "f18159b4", + "metadata": {}, + "source": [ + "2. Отметить что является классом:\n", + "\n", + "- [ ] `\"1024\"`\n", + "- [x] `str`\n", + "- [ ] `1024`\n", + "- [ ] `class`\n", + "- [x] `int`" + ] + }, + { + "cell_type": "markdown", + "id": "aec77133", + "metadata": {}, + "source": [ + "3. Куда записываются атрибуты объекта?\n", + "\n", + "- [ ] `obj.__class__`\n", + "- [ ] `obj.__attrt__`\n", + "- [ ] `obj.__doc__`\n", + "- [x] `obj.__dict__`" + ] + }, + { + "cell_type": "markdown", + "id": "d4d6f3a9", + "metadata": {}, + "source": [ + "4. Когда вызывается метод `__init__`?\n", + "\n", + "- [ ] При обращении к методу экземпляра\n", + "- [ ] При объявлении класса\n", + "- [x] При создании экземпляра" + ] + }, + { + "cell_type": "markdown", + "id": "068e0ff4", + "metadata": {}, + "source": [ + "5. Экземпляры классов хешируются?\n", + "\n", + "- [x] Да\n", + "- [ ] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "32585fbd", + "metadata": {}, + "source": [ + "6. Отметьте верные утверждения про `classmethod`\n", + "\n", + "- [x] Метод первым аргументом принимает класс\n", + "- [ ] Метод не принимает дополнительных аргументов кроме указанных программистом\n", + "- [x] К этому методу можно обращаться от экземпляра класса\n", + "- [x] К этому методу можно обращаться от имени класса\n", + "- [ ] Метод первым аргументом принимает ссылку на экземпляр класса\n" + ] + }, + { + "cell_type": "markdown", + "id": "6be13d07", + "metadata": {}, + "source": [ + "7. Отметьте верные утверждения про `staticmethod`\n", + "\n", + "- [x] К этому методу можно обращаться от экземпляра класса\n", + "- [ ] Метод первым аргументом принимает ссылку на экземпляр класса\n", + "- [ ] Метод первым аргументом принимает класс\n", + "- [x] Метод не принимает дополнительных аргументов кроме указанных программистом\n", + "- [x] К этому методу можно обращаться от имени класса" + ] + }, + { + "cell_type": "markdown", + "id": "5ccb9128", + "metadata": {}, + "source": [ + "8. Для чего используются `@property`?\n", + "\n", + "- [x] Чтобы создать вычисляемый атрибут\n", + "- [ ] Чтобы делать атрибуты приватными" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/1. Классы и объекты/Тест по классам и объектам.pdf b/3. Объектно-ориентированное программирование/1. Классы и объекты/Тест по классам и объектам.pdf new file mode 100644 index 0000000..ee8bde6 Binary files /dev/null and b/3. Объектно-ориентированное программирование/1. Классы и объекты/Тест по классам и объектам.pdf differ diff --git a/3. Объектно-ориентированное программирование/2. Наследование/Документация.ipynb b/3. Объектно-ориентированное программирование/2. Наследование/Документация.ipynb new file mode 100644 index 0000000..e47d474 --- /dev/null +++ b/3. Объектно-ориентированное программирование/2. Наследование/Документация.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fe31482e", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "ed3bdc2c", + "metadata": {}, + "source": [ + "[Наследование классов в python](https://docs.python.org/3/tutorial/classes.html#inheritance \"9.5. Inheritance\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/2. Наследование/Композиция классов.ipynb b/3. Объектно-ориентированное программирование/2. Наследование/Композиция классов.ipynb new file mode 100644 index 0000000..641f534 --- /dev/null +++ b/3. Объектно-ориентированное программирование/2. Наследование/Композиция классов.ipynb @@ -0,0 +1,524 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "59ecb880", + "metadata": {}, + "source": [ + "# Композиция классов #" + ] + }, + { + "cell_type": "markdown", + "id": "c0953a7c", + "metadata": {}, + "source": [ + "В предыдущей лекции мы рассмотрели, как работает наследование и множественное наследование в классах Python. Как я уже говорил, если создать достаточно большую иерархию классов и использовать множественное наследование, а также большое количество классов-примесей, то итоговый код может показаться очень сложным, и его будет очень трудно читать программисту и разбираться, как он работает, что делает его менее выразительным.\n", + "\n", + "В Python существует альтернативный подход наследованию — это композиция.\n", + "\n", + "В этой лекции мы с вами на примере разберем, как работает композиция, и вы сможете сравнить, какой из подходов вам больше нравится — наследование или композиция.\n", + "\n", + "Давайте немного вспомним классы. У нас был класс \"питомец\", мы от него унаследовали класс \"собачку\" (класс `Dog`). Затем мы захотели, чтобы наши объекты классов \"собака\" могли выполнять экспорт данных, и мы ввели класс-примесь `ExportJSON`. После этого наш финальный класс `ExDog` использовал множественное наследование и наследовался от класса \"собачка\" и `ExportJSON`, тем самым он решал все наши задачи." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7abf9ef1", + "metadata": {}, + "outputs": [], + "source": [ + "class Pet:\n", + " pass\n", + "\n", + "class Dog(Pet):\n", + " pass\n", + " \n", + "class ExportJSON:\n", + " pass\n", + "\n", + "class ExDog(Dog, ExportJSON):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "609625a7", + "metadata": {}, + "source": [ + "Давайте представим, что нам понадобится экспортировать данные не только в формате `json`, но и еще в другом формате, например, пусть будет это `XML`. Как тогда изменится структура нашей программы? Тогда, очевидно, нам нужно будет добавить еще один класс-примесь, и тогда наш код будет выглядеть так, как показано на слайде, то есть пока не вдаемся в подробности реализации самих классов-примесей." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e3465f4b", + "metadata": {}, + "outputs": [], + "source": [ + "class ExportJSON:\n", + " def to_json(self):\n", + " pass\n", + "\n", + "class ExportXML:\n", + " def to_xml(self):\n", + " pass\n", + " \n", + "class ExDog(Dog, ExportJSON, ExportXML):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e5b3966", + "metadata": {}, + "outputs": [], + "source": [ + "dog = ExDog(\"Фокс\", \"мопс\")\n", + "dog.to_xml()\n", + "dog.to_json()" + ] + }, + { + "cell_type": "markdown", + "id": "55fa6dc7", + "metadata": {}, + "source": [ + "У нас появился наш новый класс-примесь `ExportXML`, у него есть свой собственный метод `to_xml`, и наш итоговый класс `ExDog` теперь наследуется уже от трех классов-родителей. Тем самым объекты этого класса `ExDog` смогут экспортировать данные как в `json`, так и в `xml`.\n", + "\n", + "Давайте представим, что нам нужно будет добавлять еще несколько методов для экспорта данных. Какие сложности могут возникнуть в таком случае?\n", + "\n", + "Во-первых, нам постоянно придется изменять код нашего класса `ExDog`, постоянно дописывать туда новые классы-примеси.\n", + "\n", + "Во-вторых, это уже сильно усложнит сам код, и в итоговой программе нам нужно будет вызывать разные методы этих классов-примесей, то есть мы будем постоянно что-то изменять в своей программе. И это уже не то, чего хотелось бы нам достичь.\n", + "\n", + "Давайте попробуем рассмотреть, как в таком случае работает композиция. Для этого нам понадобится Jupyter Notebook. Давайте попробуем решить эту задачу немного другим способом." + ] + }, + { + "cell_type": "markdown", + "id": "f698b300", + "metadata": {}, + "source": [ + "Предположим, у нас будет класс для экспорта. Давайте объявим его. Пусть он будет называться `PetExport` и у него будет один метод, `export`. Обратите внимание, я сейчас использовал генерацию исключения. Об исключениях мы с вами будем говорить в последующих лекциях, а пока вам нужно для себя усвоить, что мы не будем создавать объекты данного класса `PetExport` и он предназначен только для наследования. Давайте объявим другие классы, которые будут заниматься экспортом данных. `JSON`. Также наш класс `export` должен принимать сам объект, который он будет экспортировать. Пока опустим реализацию самого экспорта. Также добавим класс для экспорта данных в формате `xml`." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b2e18c93", + "metadata": {}, + "outputs": [], + "source": [ + "class PetExport:\n", + " def export(self):\n", + " raise NotImplementedError\n", + "\n", + "class ExportJSON:\n", + " def export(self, dog):\n", + " pass\n", + " \n", + "class ExportXML:\n", + " def export(self, dog):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "b5b69659", + "metadata": {}, + "source": [ + "Итак, у нас готова иерархия классов для экспорта данных. Давайте теперь вспомним наши классы. \"Питомец\", у которого у каждого питомца было имя, мы его сохраняли в атрибуте `name`, и также класс \"собачка\", у которого появился дополнительный атрибут порода, `breed`, и мы сохранили его в одноименном атрибуте." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "14a2f4cc", + "metadata": {}, + "outputs": [], + "source": [ + "class Pet:\n", + " def __init__(self, name=None):\n", + " self.name = name\n", + " \n", + " \n", + "class Dog(Pet):\n", + " def __init__(self, name, breed=None):\n", + " super().__init__(name)\n", + " self.breed = breed" + ] + }, + { + "cell_type": "markdown", + "id": "b3b38ead", + "metadata": {}, + "source": [ + "Давайте теперь объявим класс `ExDog`. Теперь мы не будем использовать свое множественное наследование, мы будем расширять существующий класс `Dog`. Переопределим инициализатор. Он тоже будет принимать на вход имя питомца, породу, а также дополнительный объект для экспорта данных. Вызовем конструктор базового класса и сохраним объект `exporter` в `self`." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "3098e00d", + "metadata": {}, + "outputs": [], + "source": [ + "class ExDog(Dog):\n", + " def __init__(self, name, breed=None, exporter=None):\n", + " super().__init__(name, breed)\n", + " self._exporter = exporter" + ] + }, + { + "cell_type": "markdown", + "id": "40256353", + "metadata": {}, + "source": [ + "Обратите внимание, что в данном классе мы не используем наследование. Вместо этого мы используем композицию и передаем нужный объект для экспорта в инициализаторе этого класса. Давайте добавим ему метод `export`, и теперь наш класс сможет экспортировать данные." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "e3cd85ac", + "metadata": {}, + "outputs": [], + "source": [ + "class ExDog(Dog):\n", + " def __init__(self, name, breed=None, exporter=None):\n", + " super().__init__(name, breed)\n", + " self._exporter = exporter\n", + " \n", + " def export(self):\n", + " return self._exporter.export(self)" + ] + }, + { + "cell_type": "markdown", + "id": "5484df82", + "metadata": {}, + "source": [ + "Делать он это будет при помощи нашего `exporter`'а. Передадим ему `self` для экспорта. \n", + "\n", + "Давайте попробуем создать экземпляр нашего класса `ExDog`. Пусть это будет собачка \"Шарик\", порода \"Дворняга\". Предположим, мы хотим, чтобы объект этого класса умел экспортировать свои данные в `xml`. Давайте передадим нужный `exporter`." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "0a9757ad", + "metadata": {}, + "outputs": [], + "source": [ + "dog = ExDog(\"Шарик\", \"Дворняга\", exporter=ExportXML())" + ] + }, + { + "cell_type": "markdown", + "id": "63846315", + "metadata": {}, + "source": [ + "Обратите внимание, что при использовании композиции нужный объект создается именно в момент выполнения уже конкретной программы. Давайте попробуем выполнить метод `export`." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "d2a062c4", + "metadata": {}, + "outputs": [], + "source": [ + "dog.export()" + ] + }, + { + "cell_type": "markdown", + "id": "f88aac87", + "metadata": {}, + "source": [ + "Осталось реализовать только методы для экспорта в начальной иерархии классов. Давайте сделаем это. С `json` все просто, мы уже разбирали пример, используя модуль `json` и метод `dumps`." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "cfded06f", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "class ExportJSON:\n", + " def export(self, dog):\n", + " return json.dumps(\n", + " {\n", + " \"name\": dog.name,\n", + " \"breed\": dog.breed\n", + " }\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "09aad031", + "metadata": {}, + "source": [ + "Давайте реализуем теперь метод `export` в классе `ExportXML`. Реализация может выглядеть достаточно просто. Создаем сам `xml`. В нем будут объекты `name` и порода нашей собаки. Всё, осталось отформатировать данную строку при помощи метода `format`." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "376dfd98", + "metadata": {}, + "outputs": [], + "source": [ + "class ExportXML:\n", + " def export(self, dog):\n", + " return \"\"\"\n", + "\n", + " {name}\n", + " {breed}\n", + "\n", + "\"\"\".format(name=dog.name, breed=dog.breed)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "bcfad312", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'\\n\\n Шарик\\n Дворняга\\n\\n'" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dog = ExDog(\"Шарик\", \"Дворняга\", exporter=ExportXML())\n", + "dog.export()" + ] + }, + { + "cell_type": "markdown", + "id": "5be78f28", + "metadata": {}, + "source": [ + "Точно так же мы с легкостью можем сделать экспорт данных и в формате `json`. Можем создать другую собачку. Пусть это будет \"Тузик\" другой породы." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "ef8c4c01", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'{\"name\": \"\\\\u0422\\\\u0443\\\\u0437\\\\u0438\\\\u043a\", \"breed\": \"\\\\u041c\\\\u043e\\\\u043f\\\\u0441\"}'" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dog = ExDog(\"Тузик\", \"Мопс\", exporter=ExportJSON())\n", + "dog.export()" + ] + }, + { + "cell_type": "markdown", + "id": "c52e1bfa", + "metadata": {}, + "source": [ + "Однако, неудобно, если каждый раз задавать метод для экспорта. Давайте немного изменим наш класс `ExDog` и зададим метод для экспорта по умолчанию. Можно это сделать следующим образом. \n", + "\n", + "Давайте выполним еще одну важную вещь. Мы проверим на то, является ли переданный объект экземпляром класса `PetExport` и вообще может ли он выполнять экспорт данных. Для этого мы можем воспользоваться проверкой `isinstance`. Нам нужен наш `exporter` и указываем класс. Что делать, если нам передали объект, который не может выполнять экспорт? Давайте пока сгенерируем исключение.\n", + "Для вас это будет означать, что программа дальше не сможет продолжить свою работу и будет остановлена. Например, `ValueError` нам подойдет." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "624c763b", + "metadata": {}, + "outputs": [], + "source": [ + "class ExDog(Dog):\n", + " def __init__(self, name, breed=None, exporter=None):\n", + " super().__init__(name, breed)\n", + " \n", + " self._exporter = exporter or ExportJSON()\n", + " \n", + " if not isinstance(self._exporter, PetExport):\n", + " raise ValueError(\"bad exporter\", exporter)\n", + " \n", + " def export(self):\n", + " return self._exporter.export(self)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "812aff80", + "metadata": {}, + "outputs": [], + "source": [ + "class ExportJSON(PetExport):\n", + " def export(self, dog):\n", + " return json.dumps(\n", + " {\n", + " \"name\": dog.name,\n", + " \"breed\": dog.breed\n", + " }\n", + " )\n", + "\n", + "class ExportXML(PetExport):\n", + " def export(self, dog):\n", + " return \"\"\"\n", + "\n", + " {name}\n", + " {breed}\n", + "\n", + "\"\"\".format(name=dog.name, breed=dog.breed)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "41174209", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'{\"name\": \"\\\\u0422\\\\u0443\\\\u0437\\\\u0438\\\\u043a\", \"breed\": \"\\\\u041c\\\\u043e\\\\u043f\\\\u0441\"}'" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dog = ExDog(\"Тузик\", \"Мопс\")\n", + "dog.export()" + ] + }, + { + "cell_type": "markdown", + "id": "fe1efbd1", + "metadata": {}, + "source": [ + "Теперь, если мы не объявим объект `exporter`, по умолчанию наша собачка будет экспортировать данные в `json`. Давайте еще раз посмотрим на то, какой код у нас получился, и подумаем, что с ним произойдет, если его нужно будет расширить. " + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "4bb4fda8", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "class Pet:\n", + " def __init__(self, name=None):\n", + " self.name = name\n", + " \n", + " \n", + "class Dog(Pet):\n", + " def __init__(self, name, breed=None):\n", + " super().__init__(name)\n", + " self.breed = breed\n", + " \n", + " \n", + "class PetExport:\n", + " def export(self):\n", + " raise NotImplementedError\n", + "\n", + " \n", + "class ExportJSON(PetExport):\n", + " def export(self, dog):\n", + " return json.dumps(\n", + " {\n", + " \"name\": dog.name,\n", + " \"breed\": dog.breed\n", + " }\n", + " )\n", + "\n", + " \n", + "class ExportXML(PetExport):\n", + " def export(self, dog):\n", + " return \"\"\"\n", + "\n", + " {name}\n", + " {breed}\n", + "\n", + "\"\"\".format(name=dog.name, breed=dog.breed)\n", + " \n", + " \n", + "class ExDog(Dog):\n", + " def __init__(self, name, breed=None, exporter=None):\n", + " super().__init__(name, breed)\n", + " \n", + " self._exporter = exporter or ExportJSON()\n", + " \n", + " if not isinstance(self._exporter, PetExport):\n", + " raise ValueError(\"bad exporter\", exporter)\n", + " \n", + " def export(self):\n", + " return self._exporter.export(self)" + ] + }, + { + "cell_type": "markdown", + "id": "291bee59", + "metadata": {}, + "source": [ + "Предположим, если нам нужно будет добавить в этот код новый метод для экспорта, мы с легкостью сможем сделать это. Просто объявим новый класс, добавим его в существующую иерархию для экспорта, а класс `ExDog` теперь мы больше менять не будем. Таким образом, если у нас система будет усложняться, наш класс `ExDog` будет оставаться неизменяемым или неизменным. А экспортировать в различные форматы мы уже сможем легко и удобно в итоговой программе, подставив нужный exporter или создав его.\n", + "\n", + "Не пугайтесь, если вам сейчас непонятны плюсы данного подхода по отношению к наследованию. Чем вы больше будете программировать на Python, пробовать и реализовывать различные подходы, тем вам станет понятно, где действительно лучше применять наследование, а в каких местах следует использовать композицию." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/2. Наследование/Наследование в Python.ipynb b/3. Объектно-ориентированное программирование/2. Наследование/Наследование в Python.ipynb new file mode 100644 index 0000000..5e20e82 --- /dev/null +++ b/3. Объектно-ориентированное программирование/2. Наследование/Наследование в Python.ipynb @@ -0,0 +1,618 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "20e9c323", + "metadata": {}, + "source": [ + "# Наследование в Python #" + ] + }, + { + "cell_type": "markdown", + "id": "19ca4a24", + "metadata": {}, + "source": [ + "Вы уже знаете достаточно большое количество информации о языке Python.\n", + "\n", + "Например, то, что всё в Python — это объект, и это очень сильно отличает его от других языков программирования.\n", + "\n", + "Также вы умеете объявлять свои собственные классы, добавлять к ним атрибуты, создавать методы, а также создавать экземпляры этих классов и обращаться к атрибутам и методам.\n", + "\n", + "Однако это далеко не все возможности объектно-ориентированного языка Python. Сегодня мы углубимся в детали реализации классов и поговорим про наследование.\n", + "\n", + "Для чего же нужно наследование классов? Прежде всего оно нужно для изменения поведения конкретного класса, а также расширения его функционала. Давайте представим, что нам необходимо программировать процессы, которые происходят на нашей планете Земля, и мы хотим населить нашу планету Земля домашними питомцами.\n", + "\n", + "Предположим, у нас есть класс, назовем его \"питомец\" или \"домашний питомец\". И у каждого домашнего питомца есть имя." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "54da589d", + "metadata": {}, + "outputs": [], + "source": [ + "class Pet:\n", + " def __init__(self, name=None):\n", + " self.name = name" + ] + }, + { + "cell_type": "markdown", + "id": "3ed53134", + "metadata": {}, + "source": [ + "Нам неинтересно населять планету Земля непонятными питомцами и давайте попробуем ее населить, например, собаками." + ] + }, + { + "cell_type": "markdown", + "id": "103a7252", + "metadata": {}, + "source": [ + "Для этого нам поможет наследование. Класс \"питомец\" может использоваться уже другими программистами, и нам не хотелось бы его менять. Поэтому давайте попробуем расширить его.\n", + "\n", + "Наследование в Python выглядит очень просто, мы объявляем класс \"собака\", класс `Dog`, и в скобках указываем родительский класс \"питомец\". Новый класс, созданный при помощи наследования, наследует все атрибуты и методы родительского класса. В данном случае класс \"питомец\" является родительским классом, его также еще называют базовым классом или суперклассом. А класс \"собака\" называется дочерним классом или классом-наследником." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b28d0ecd", + "metadata": {}, + "outputs": [], + "source": [ + "class Dog(Pet):\n", + " def __init__(self, name, breed=None):\n", + " super().__init__(name)\n", + " self.breed = breed\n", + " \n", + " def say(self):\n", + " return f\"{self.name}: waw\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7248d621", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Шарик\n", + "Шарик: waw\n" + ] + } + ], + "source": [ + "dog = Dog(\"Шарик\", \"Доберман\")\n", + "print(dog.name)\n", + "print(dog.say())" + ] + }, + { + "cell_type": "markdown", + "id": "f61360a2", + "metadata": {}, + "source": [ + "Давайте попробуем изменить поведение нашего класса \"питомец\". Для этого мы переопределим метод `init`, или инициализатор класса, и добавим новый атрибут `breed`, в котором будем хранить породу собаки. Если этот атрибут равен значению `None`, то порода не определена. Также обратите внимание, мы в нашем методе `init` вызываем метод `init` из родительского класса. Для этого мы пользуемся функцией `super`.\n", + "\n", + "Хочу обратить ваше внимание, что мы именно не скопировали код из родительского класса, а именно обратились, вызвали его, тем самым мы расширили поведение этого класса.\n", + "\n", + "Также мы можем добавить, кроме атрибутов, и собственный метод. Давайте назовем его `say` и напишем реализацию. Таким образом, наши питомцы, или наши собаки, умеют подавать голос, если мы обратимся к этому методу `say`.\n", + "\n", + "Ну что же, мы создали класс при помощи наследования, который решает нужные нам задачи. Вы можете создать свои классы, унаследовать от класса \"питомец\" и населить планету Земля различными животными, птицами, рыбами, котами, какими угодно." + ] + }, + { + "cell_type": "markdown", + "id": "aecc21a3", + "metadata": {}, + "source": [ + "В Python разрешено наследование от нескольких классов предков, или как это еще называется, множественное наследование. Очень часто этот прием используется для реализации класса примесей. \n", + "\n", + "Предположим, что нам необходимо экспортировать данные о наших объектах собачках в формате `json`, для того чтобы хранить эти данные на жестком диске, либо передавать по сети. Мы можем решить подобную задачу при помощи класса примесей и множественного наследования. Объявим класс `ExportJSON`, реализуем метод, который экспортирует данные в формате `json`, и создадим новый класс, который называется `ExDog`, и он будет наследоваться от класса \"собака\", `dog`, и нашего нового класса примесей, `ExportJSON`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "5ac468ac", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "class ExportJSON:\n", + " def to_json(self):\n", + " return json.dumps(\n", + " {\n", + " \"name\": self.name,\n", + " \"breed\": self.breed\n", + " }\n", + " )\n", + "\n", + "class ExDog(Dog, ExportJSON):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2a5229c2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"name\": \"\\u0411\\u0435\\u043b\\u043a\\u0430\", \"breed\": \"\\u0414\\u0432\\u043e\\u0440\\u043d\\u044f\\u0436\\u043a\\u0430\"}\n" + ] + } + ], + "source": [ + "dog = ExDog(\"Белка\", breed=\"Дворняжка\")\n", + "print(dog.to_json())" + ] + }, + { + "cell_type": "markdown", + "id": "8c6e5c84", + "metadata": {}, + "source": [ + "Созданный класс при помощи множественного наследования объединяет в себе свойства всех родительских классов. Если мы создадим объект `dog`, который будет являться экземпляром нашего нового класса `ExDog`, то мы сможем обратиться к методу `to_json`, который является методом примесей `ExportJSON`.\n", + "\n", + "С одной стороны, это кажется очень удобным и гибким, однако множественное наследование и использование большого количества примесей имеет ряд своих недостатков и минусов.\n", + "\n", + "Если у вас будет очень сложная иерархия классов и большое количество примесей, то код станет менее выразительным, и программисту, который разбирается с вашим кодом, будет достаточно тяжело его читать. Поэтому не стоит сильно увлекаться и создавать большое количество классов примесей.\n", + "\n", + "Любой класс в Python является потомком класса `object`. Мы можем легко убедиться в этом, если попробуем использовать функцию `issubclass`. Например, мы можем ее вызвать для класса `int`, и она вернет значение \"истина\". Также мы можем попробовать проверить, является ли наш класс `Dog`, \"собачка\", потомком класса `object` или потомком класса \"питомец\"." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b5a24c8d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "issubclass(int, object)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "365c1aec", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "issubclass(Dog, object)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "ed18b7da", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "issubclass(Dog, Pet)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4a623d15", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "issubclass(Dog, int)" + ] + }, + { + "cell_type": "markdown", + "id": "c6af4304", + "metadata": {}, + "source": [ + "Однако наш класс \"собака\" не является потомком для класса `int`, и эта функция вернет значение \"ложь\". Также при помощи функции `isinstance` мы можем проверять, является ли конкретный объект экземпляром нужного нам класса, и, например, мы можем создать объект \"собака\" и проверить, является ли наш объект экземпляром класса `Dog`, `Pet` или `Object`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1bb8a142", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(dog, Dog)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ac1239f4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(dog, Pet)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "8bfa0602", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(dog, object)" + ] + }, + { + "cell_type": "markdown", + "id": "1c2b0b7f", + "metadata": {}, + "source": [ + "Все эти три вызова вернут значение \"истина\". Вы можете использовать эти функции `issubclass`, `isinstance` в своем коде, они очень часто вам будут нужны.\n", + "\n", + "При помощи наследования Python позволяет выстраивать достаточно сложные иерархии классов. Несмотря на то, что мы создали небольшое количество классов, давайте взглянем на то, как выглядит наша полученная иерархия в итоге." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "7c8d63fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(__main__.ExDog, __main__.Dog, __main__.Pet, __main__.ExportJSON, object)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"\"\"\n", + "\n", + " object\n", + " / \\\n", + "Pet ExportJSON\n", + " | /\n", + "Dog /\n", + " \\ /\n", + " ExDog\n", + "\n", + "\"\"\"\n", + "\n", + "ExDog.__mro__" + ] + }, + { + "cell_type": "markdown", + "id": "821a6937", + "metadata": {}, + "source": [ + "Так, у нас есть класс `ExDog`, который мы создали при помощи множественного наследования от класса `Dog` и класса примесей `ExportJSON`. В свою очередь, класс `Dog` наследуется от класса \"питомец\", и все остальные классы наследуются от класса `object`. Если мы попробуем создать экземпляр класса `ExDog` и обратиться к атрибуту `name`, то как же Python будет искать этот атрибут в существующей иерархии классов? Для этого в Python существует так называемый `Method Resolution Order`, или порядок разрешения методов, и он устроен не так просто, как вам бы могло показаться. \n", + "\n", + "Однако, и вообще, в целом это отдельная тема для изучения. Однако все, что вам нужно знать, это порядок, в котором Python ищет нужный атрибут или метод. Итак, у нас есть иерархия классов, связи, все родительские, прародительские классы, и обратиться к этому списку можно при помощи атрибута `__mro__`. Если мы попробуем обратиться к атрибуту `name`, то Python будет искать сначала в классе `ExDog`, затем `Dog`, и после того как он обратится к классу `Pet`, нужный атрибут `name` будет найден. Данный список еще называется линеаризацией класса, то есть Python любые атрибуты и методы ищет в этом списке линеаризации. Если он пройдется по всему списку и не найдет, то будет сгенерировано исключение `AttributeError`." + ] + }, + { + "cell_type": "markdown", + "id": "68e92ddb", + "metadata": {}, + "source": [ + "Про исключения мы с вами будем говорить позже. Теперь вы знаете, как Python ищет атрибуты и методы в сложной иерархии классов. Двигаемся дальше.\n", + "\n", + "В самом начале, когда мы создавали класс `Dog`, мы рассматривали вызов инициализатора базового класса. Это выглядит достаточно просто, мы просто вызываем `super` без параметров, и происходит вызов функции или метода из базового класса. Однако в Python можно обратиться не только к базовому классу, но и к любому методу в существующей иерархии. Как это делается? Давайте рассмотрим очередной пример." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a08baca6", + "metadata": {}, + "outputs": [], + "source": [ + "class ExDog(Dog, ExportJSON):\n", + " def __init__(self, name, breed=None):\n", + " super().__init__(name, breed)\n", + " # super(ExDog, self).__init__(name)" + ] + }, + { + "cell_type": "markdown", + "id": "59557161", + "metadata": {}, + "source": [ + "Вызов функции `super` без параметров равносилен тому, если б мы указали сам класс и передали туда объект `self`. Опускать параметры очень удобно и зачастую вам часто именно так и придется делать. Однако если вам необходимо вызвать метод конкретного класса, то в функцию `super` надо передать его родителя. Хочу обратить ваше внимание, что именно нужно передавать родителя. Итак, если мы создадим новый класс `WoolenDog` и захотим обратиться к инициализатору класса \"питомец\", то нам необходимо в функции `super` указать класс \"родитель\". Если мы попробуем создать объект класса `Dog` и обратиться к атрибуту `breed`, то получим вывод, который показан на слайде." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "25b98630", + "metadata": {}, + "outputs": [], + "source": [ + "class WoolenDog(Dog, ExportJSON):\n", + " def __init__(self, name, breed=None):\n", + " super(Dog, self).__init__(name)\n", + " self.breed = f\"Шерстяная собака породы {breed}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d74097fc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Шерстяная собака породы Такса\n" + ] + } + ], + "source": [ + "dog = WoolenDog(\"Жучка\", breed=\"Такса\")\n", + "print(dog.breed)" + ] + }, + { + "cell_type": "markdown", + "id": "ff956135", + "metadata": {}, + "source": [ + "Тем самым, вы теперь знаете, как работает функция `super` и как можно обратиться к любому методу в сложной иерархии классов.\n", + "\n", + "Также в Python существуют приватные атрибуты. Давайте поговорим немного о них. Для того чтобы создать приватный атрибут, необходимо его имя записать через два символа нижнего подчёркивания. Предположим, мы атрибут `breed` или породу собак решили сделать приватным, объявили его следующим образом, тогда в самом классе к нему можно обращаться так же, а вот для классов-наследников этот атрибут будет уже недоступен." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "fc92c066", + "metadata": {}, + "outputs": [], + "source": [ + "class Dog(Pet):\n", + " def __init__(self, name, breed=None):\n", + " super().__init__(name)\n", + " self.__breed = breed\n", + " \n", + " def say(self):\n", + " return f\"{self.name}: waw\"\n", + " \n", + " def get_breed(self):\n", + " return self.__breed" + ] + }, + { + "cell_type": "markdown", + "id": "b94634e4", + "metadata": {}, + "source": [ + "Давайте попробуем выполнить код, который указан на слайде в Python ноутбуке. Для этого нам потребуется код этих классов. Давайте сделаем это. Итак, у нас есть класс \"Собака\", который унаследован от \"Питомца\". У класса \"Собаки\" есть приватный атрибут `breed` и обращение к нему. Итак, наш итоговый класс `ExDog`, в котором есть функция `get_breed`, и в ней записано обращение к приватному атрибуту суперкласса." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "79743a39", + "metadata": {}, + "outputs": [], + "source": [ + "class ExDog(Dog, ExportJSON):\n", + " def get_breed(self):\n", + " return f\"Порода: {self.name} - {self.__breed}\"" + ] + }, + { + "cell_type": "markdown", + "id": "3b9ebc1f", + "metadata": {}, + "source": [ + "Давайте попробуем создать объект нашего класса `ExDog`. Пусть собаку зовут \"Тузик\", и её порода будет \"питбуль\". Давайте теперь попробуем вызвать метод `get_breed` и попробуем получить её породу." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "9dd3da45", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'Фокс', '_Dog__breed': 'Мопс'}" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dog = ExDog(\"Фокс\", \"Мопс\")\n", + "dog.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "cc812895", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'ExDog' object has no attribute '_ExDog__breed'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mdog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_breed\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m\u001b[0m in \u001b[0;36mget_breed\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mclass\u001b[0m \u001b[0mExDog\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mDog\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mExportJSON\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_breed\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[1;34mf\"Порода: {self.name} - {self.__breed}\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m: 'ExDog' object has no attribute '_ExDog__breed'" + ] + } + ], + "source": [ + "dog.get_breed()" + ] + }, + { + "cell_type": "markdown", + "id": "a685d675", + "metadata": {}, + "source": [ + "Как мы видим, у нас не получилось это сделать, возникло исключение `AttributeError`. Давайте попробуем разобраться, в чём же дело. Можно распечатать внутренний атрибут `__dict__`, который нам покажет все атрибуты нашего созданного объекта. Итак, мы видим, что вместо атрибута `__breed` Python автоматически поставил имя класса. \n", + "\n", + "В целом, если нам очень нужно обратиться к этому приватному атрибуту, Python позволяет это сделать, и мы можем исправить код нашего класса, добавить префикс с классом Dog и попробовать ещё раз обратиться к нему." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "3076e8c5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Порода: Фокс - Мопс'" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "class ExDog(Dog, ExportJSON):\n", + " def get_breed(self):\n", + " return f\"Порода: {self.name} - {self._Dog__breed}\"\n", + " \n", + "dog = ExDog(\"Фокс\", \"Мопс\")\n", + "dog.get_breed()" + ] + }, + { + "cell_type": "markdown", + "id": "6bac3f91", + "metadata": {}, + "source": [ + "Итак, у нас получилось. То есть, как я уже сказал, Python позволяет обращаться к приватным атрибутам класса вне самого класса, однако, лучше сильно этим не увлекаться.\n", + "\n", + "Мы обсудили с вами то, как устроено наследование классов в Python, мы поговорили про множественное наследование, обсудили, как Python ищет атрибуты и методы в сложной иерархии классов. Также поговорили о вызове методов при помощи функции `super` в сложной иерархии классов и обсудили приватные атрибуты или `name mangling`. \n", + "\n", + "На следующей лекции мы обсудим ещё один интересный подход, который называется композицией, и сравним его с наследованием." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/2. Наследование/Тест по наследованию (Clear).ipynb b/3. Объектно-ориентированное программирование/2. Наследование/Тест по наследованию (Clear).ipynb new file mode 100644 index 0000000..050bd3e --- /dev/null +++ b/3. Объектно-ориентированное программирование/2. Наследование/Тест по наследованию (Clear).ipynb @@ -0,0 +1,107 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "76f6cab6", + "metadata": {}, + "source": [ + "# Тест по наследованию #" + ] + }, + { + "cell_type": "markdown", + "id": "3799df11", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "2b586a08", + "metadata": {}, + "source": [ + "##### 1. Наследование классов нужно:\n", + "\n", + "- [ ] для создания экземпляров класса\n", + "- [ ] для изменения поведения класса\n", + "- [ ] для расширения функционала класса\n", + "- [ ] для ограничения доступа к атрибутам класса предка" + ] + }, + { + "cell_type": "markdown", + "id": "fe834941", + "metadata": {}, + "source": [ + "##### 2. Выберите истинные утверждения\n", + "\n", + "- [ ] Для вызова нужного метода используется линеаризация класса\n", + "- [ ] В **Python** разрешено множественное наследование\n", + "- [ ] Все классы в **python** унаследованы от класса `object`\n", + "- [ ] Классы-примеси используются в множественном наследовании" + ] + }, + { + "cell_type": "markdown", + "id": "353fd015", + "metadata": {}, + "source": [ + "##### 3. Предположим есть базовый класс питомец - `Pet` и класс наследник - `Dog`. Отметьте все варианты вызова метода `Pet.__init__` из инициализатора класса потомка.\n", + "\n", + "- [ ] `super(Dog, self).__init__()`\n", + "- [ ] `super(Pet, self).__init__()`\n", + "- [ ] `super().__init__()`" + ] + }, + { + "cell_type": "markdown", + "id": "7d3d9230", + "metadata": {}, + "source": [ + "##### 4. Предположим есть базовый класс питомец - `Pet` и класс наследник - `Dog`. Отметьте варианты, которые вернут `True`\n", + "\n", + "- [ ] `issubclass(Pet, Dog)`\n", + "- [ ] `issubclass(Dog, object)`\n", + "- [ ] `issubclass(Pet, object)`\n", + "- [ ] `issubclass(Dog, Pet)`" + ] + }, + { + "cell_type": "markdown", + "id": "24f973e5", + "metadata": {}, + "source": [ + "##### 5. Предположим есть базовый класс питомец - `Pet` и класс наследник - `Dog`. Отметьте варианты, которые вернут `True`\n", + "\n", + "- [ ] `isinstance(Dog(), Dog)`\n", + "- [ ] `isinstance(Dog(), Pet)`\n", + "- [ ] `isinstance(Dog, Dog)`\n", + "- [ ] `isinstance(Pet(), Dog)`\n", + "- [ ] `isinstance(Pet(), object)`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/2. Наследование/Тест по наследованию.ipynb b/3. Объектно-ориентированное программирование/2. Наследование/Тест по наследованию.ipynb new file mode 100644 index 0000000..8592729 --- /dev/null +++ b/3. Объектно-ориентированное программирование/2. Наследование/Тест по наследованию.ipynb @@ -0,0 +1,98 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "76f6cab6", + "metadata": {}, + "source": [ + "# Тест по наследованию #" + ] + }, + { + "cell_type": "markdown", + "id": "2b586a08", + "metadata": {}, + "source": [ + "1. Наследование классов нужно:\n", + "\n", + "- [ ] для создания экземпляров класса\n", + "- [x] для изменения поведения класса\n", + "- [x] для расширения функционала класса\n", + "- [ ] для ограничения доступа к атрибутам класса предка" + ] + }, + { + "cell_type": "markdown", + "id": "fe834941", + "metadata": {}, + "source": [ + "2. Выберите истинные утверждения\n", + "\n", + "- [x] Для вызова нужного метода используется линеаризация класса\n", + "- [x] В **Python** разрешено множественное наследование\n", + "- [x] Все классы в **python** унаследованы от класса `object`\n", + "- [x] Классы-примеси используются в множественном наследовании" + ] + }, + { + "cell_type": "markdown", + "id": "353fd015", + "metadata": {}, + "source": [ + "3. Предположим есть базовый класс питомец - `Pet` и класс наследник - `Dog`. Отметьте все варианты вызова метода `Pet.__init__` из инициализатора класса потомка.\n", + "\n", + "- [x] `super(Dog, self).__init__()`\n", + "- [ ] `super(Pet, self).__init__()`\n", + "- [x] `super().__init__()`" + ] + }, + { + "cell_type": "markdown", + "id": "7d3d9230", + "metadata": {}, + "source": [ + " 4. Предположим есть базовый класс питомец - `Pet` и класс наследник - `Dog`. Отметьте варианты, которые вернут `True`\n", + "\n", + "- [ ] `issubclass(Pet, Dog)`\n", + "- [x] `issubclass(Dog, object)`\n", + "- [x] `issubclass(Pet, object)`\n", + "- [x] `issubclass(Dog, Pet)`" + ] + }, + { + "cell_type": "markdown", + "id": "24f973e5", + "metadata": {}, + "source": [ + "5. Предположим есть базовый класс питомец - `Pet` и класс наследник - `Dog`. Отметьте варианты, которые вернут `True`\n", + "\n", + "- [x] `isinstance(Dog(), Dog)`\n", + "- [x] `isinstance(Dog(), Pet)`\n", + "- [ ] `isinstance(Dog, Dog)`\n", + "- [ ] `isinstance(Pet(), Dog)`\n", + "- [x] `isinstance(Pet(), object)`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/2. Наследование/Тест по наследованию.pdf b/3. Объектно-ориентированное программирование/2. Наследование/Тест по наследованию.pdf new file mode 100644 index 0000000..5938294 Binary files /dev/null and b/3. Объектно-ориентированное программирование/2. Наследование/Тест по наследованию.pdf differ diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/cars.csv b/3. Объектно-ориентированное программирование/3. Работа с ошибками/cars.csv new file mode 100644 index 0000000..fe9c75e --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/cars.csv @@ -0,0 +1,8 @@ +car_type;brand;passenger_seats_count;photo_file_name;body_whl;carrying;extra +car;Nissan xTtrail;4;f1.jpeg;;2.5; +truck;Man;;f2.png;8x3x2.5;20; +truck;Man;;f2.png;;20; +car;Mazda 6;4;f3.jpeg;;2.5; +;;; +spec_machine;Hitachi;;f4;;1.2;Легкая техника для уборки снега + diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/cars.py b/3. Объектно-ориентированное программирование/3. Работа с ошибками/cars.py new file mode 100644 index 0000000..9040a5a --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/cars.py @@ -0,0 +1,142 @@ +"""Решение задачи про классы и наследование""" + +from csv import reader +from functools import reduce +from os.path import splitext +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List + + +class CarBase: + """Базовый класс для автомобилей и спецтехники""" + + def __init__( + self, + brand: "str", + photo_file_name: "str", + carrying: "float", + car_type: "str" = "car", + ): + """Конструктор базового класса""" + + self.brand: "str" = brand + self.photo_file_name: "str" = photo_file_name + self.carrying: "float" = carrying + self.__car_type: "str" = car_type + + def __repr__(self) -> "str": + """Строковое представления объекта""" + + return f"{self.car_type}: {self.brand} {self.carrying}" + + @property + def car_type(self) -> "str": + """Тип автомобиля или спецтехники""" + + return self.__car_type + + def get_photo_file_ext(self) -> "str": + """Предоставляет расширение файла с фото""" + + return splitext(self.photo_file_name)[1] + + +class Car(CarBase): + """Класс легковых автомобилей""" + + def __init__( + self, + brand: "str", + photo_file_name: "str", + carrying: "float", + passenger_seats_count: "int", + ): + """Конструктор класса легковых автомобилей""" + + super().__init__(brand, photo_file_name, carrying) + self.passenger_seats_count: "int" = passenger_seats_count + + +class Truck(CarBase): + """Класс грузовых автомобилей""" + + def __init__( + self, + brand: "str", + photo_file_name: "str", + carrying: "float", + body_whl: "str", + ): + """Конструктор класса грузовых автомобилей""" + + super().__init__(brand, photo_file_name, carrying, "truck") + + self.body_width: "float" = 0.0 + self.body_height: "float" = 0.0 + self.body_length: "float" = 0.0 + + if body_whl: + try: + self.body_width, self.body_height, self.body_length = tuple( + map(float, body_whl.split("x", maxsplit=2)) + ) + + except ValueError as error: + raise ValueError( + "Invalid format argument body_whl." + ) from error + + def get_body_volume(self) -> "float": + """Возвращает объем кузова в метрах кубических""" + + return reduce( + lambda x, y: x * y, + (self.body_width, self.body_height, self.body_length), + ) + + +class SpecMachine(CarBase): + """Класс спецтехники""" + + def __init__( + self, + brand: "str", + photo_file_name: "str", + carrying: "float", + extra: "str", + ): + """Конструктор класса спецтехники""" + + super().__init__(brand, photo_file_name, carrying, "spec_machine") + self.extra: "str" = extra + + +def get_car_list(csv_filename: "str") -> "List[CarBase]": + """Чтение данных из csv файла и представление их в виде списка объектов""" + + car_list: "List[CarBase]" = [] + + with open(csv_filename, encoding="UTF-8") as csv_fd: + for i, row in enumerate(reader(csv_fd, delimiter=";")): + if i and len(row) == 7: + try: + if row[0] == "car": + car_list.append( + Car(row[1], row[3], float(row[5]), int(row[2])) + ) + + elif row[0] == "truck": + car_list.append( + Truck(row[1], row[3], float(row[5]), row[4]) + ) + + elif row[0] == "spec_machine": + car_list.append( + SpecMachine(row[1], row[3], float(row[5]), row[6]) + ) + except ValueError: + continue + + return car_list diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/ex.py b/3. Объектно-ориентированное программирование/3. Работа с ошибками/ex.py new file mode 100644 index 0000000..c5175fc --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/ex.py @@ -0,0 +1,6 @@ +def get_user_by_id(id): + assert isinstance(id, int), "id должен быть целым числом" + print("выполняем поиск") + +if __name__ == "__main__": + get_user_by_id("foo") \ No newline at end of file diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/solution.py b/3. Объектно-ориентированное программирование/3. Работа с ошибками/solution.py new file mode 100644 index 0000000..90001fe --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/solution.py @@ -0,0 +1,27 @@ +"""Реализация простого класса для чтения из файла""" + + +class FileReader: + """Реализация класса для чтения из файла""" + + def __init__(self, file_path: "str"): + """Конструктор класса""" + + self.file_path: "str" = file_path + + def __repr__(self) -> "str": + """Строковое представления объекта""" + + return f"FileReader({self.file_path!r})" + + def read(self) -> "str": + """Чтение файла""" + + try: + with open(self.file_path, encoding="utf-8") as des: + content: "str" = des.read() + + except IOError: + content = "" + + return content diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/wc.py b/3. Объектно-ориентированное программирование/3. Работа с ошибками/wc.py new file mode 100644 index 0000000..a7c1a00 --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/wc.py @@ -0,0 +1,21 @@ +import sys + +def wc(filename): + count = 0 + + with open(filename) as f: + for _ in f: + count += 1 + + return count + +def process_file(filename): + count = wc(filename) + + print(f"file: {filename} has {count} lines") + +def _main(): + process_file(sys.argv[1]) + +if __name__ == "__main__": + _main() \ No newline at end of file diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/wget.py b/3. Объектно-ориентированное программирование/3. Работа с ошибками/wget.py new file mode 100644 index 0000000..86c01fc --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/wget.py @@ -0,0 +1,21 @@ +import sys +import requests + +url = sys.argv[1] + +try: + response = requests.get(url, timeout=30) + response.raise_for_status() + +except requests.Timeout: + print("Ошибка timeout, url:", url) + +except requests.HTTPError as err: + code = err.response.status_code + print(f"Ошибка url: {url}, code: {code}") + +except requests.RequestException: + print("Ошибка скачивания url:", url) + +else: + print(response.content) \ No newline at end of file diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/zero.py b/3. Объектно-ориентированное программирование/3. Работа с ошибками/zero.py new file mode 100644 index 0000000..53aa48d --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/zero.py @@ -0,0 +1 @@ +1/0 \ No newline at end of file diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Генерация исключений.ipynb b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Генерация исключений.ipynb new file mode 100644 index 0000000..628d00a --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Генерация исключений.ipynb @@ -0,0 +1,540 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4b201c2f", + "metadata": {}, + "source": [ + "# Генерация исключений #" + ] + }, + { + "cell_type": "markdown", + "id": "249ae766", + "metadata": {}, + "source": [ + "На предыдущей лекции мы начали с вами свое знакомство с исключениями, и в этой лекции мы продолжим разбирать их работу и обсудим более подробно, как обрабатывать исключения, как получать доступ к объекту исключения, а также обсудим отдельные исключения вида `AssertionError` и вопросы производительности исключений." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5677dc5b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 No such file or directory\n" + ] + } + ], + "source": [ + "try:\n", + " with open(\"/file/not/found\") as f:\n", + " content = f.read()\n", + " \n", + "except OSError as err:\n", + " print(err.errno, err.strerror)" + ] + }, + { + "cell_type": "markdown", + "id": "bba1230f", + "metadata": {}, + "source": [ + "Для того чтобы получить доступ к объекту исключений, нам необходимо воспользоваться конструкцией `except as err`. В данном примере, если будет сгенерировано исключение `OSError`, то сам объект исключений будет связан с переменной `err` и эта переменная `err` будет доступна в блоке `except`." + ] + }, + { + "cell_type": "markdown", + "id": "56767c12", + "metadata": {}, + "source": [ + "У каждого объекта типа исключений есть свои свойства, например, `errno` и `srterror` — это строковое описание ошибки и код ошибки. При помощи этих атрибутов можно получать доступ и обрабатывать исключения нужным вам образом. Очень часто используется атрибут `args` для доступа к атрибутам исключения." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0c4d7d62", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "файл не существует /file/not/found\n" + ] + } + ], + "source": [ + "import os.path\n", + "\n", + "filename = \"/file/not/found\"\n", + "\n", + "try:\n", + " if not os.path.exists(filename):\n", + " raise ValueError(\"файл не существует\", filename)\n", + " \n", + "except ValueError as err:\n", + " message, filename = err.args[0], err.args[1]\n", + " print(message, filename)" + ] + }, + { + "cell_type": "markdown", + "id": "e3e20202", + "metadata": {}, + "source": [ + "Предположим, мы проверили, что файл не существует, и сгенерировали исключение `ValueError`, передали туда строку и имя файла. Так вот, если мы в блоке `except` обратимся к объекту исключения, то у него будут доступен атрибут `args` — это список из наших параметров и можем делать с ними все, что захотим.\n", + "\n", + "Иногда нам может потребоваться стек вызовов при генерации исключения. Стек вызовов можно получить при помощи модуля `traceback` и вызвать метод `print_exc`. Давайте попробуем выполнить этот пример в консоли и посмотрим, что выведется на экран." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "21569499", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "None\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Traceback (most recent call last):\n", + " File \"\", line 4, in \n", + " with open(\"/file/not/found\") as f:\n", + "FileNotFoundError: [Errno 2] No such file or directory: '/file/not/found'\n" + ] + } + ], + "source": [ + "import traceback\n", + "\n", + "try:\n", + " with open(\"/file/not/found\") as f:\n", + " content = f.read()\n", + "\n", + "except OSError as err:\n", + " trace = traceback.print_exc()\n", + " print(trace)" + ] + }, + { + "cell_type": "markdown", + "id": "06d47261", + "metadata": {}, + "source": [ + "Итак, у нас получилось распечатать стек вызовов. Иногда стек вызовов может вам помочь при разбирательстве причин, при которых возникло исключение. \n", + "\n", + "Для того чтобы сгенерировать, собственно, исключение, вам необходима инструкция `raise`. Для генерации исключения мы должны написать `raise` и указать класс исключения. Также можно указывать не только класс, но и объект исключения, и указывать ему дополнительные какие-то свойства. Как я уже говорил, к этим свойствам потом можно будет обратится через объект исключения при помощи атрибута `args`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b578c4de", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "введите число: w\n", + "некорректное значение!\n" + ] + } + ], + "source": [ + "try:\n", + " raw = input(\"введите число: \")\n", + " if not raw.isdigit():\n", + " raise ValueError\n", + " \n", + "except ValueError:\n", + " print(\"некорректное значение!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "28e93b13", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "введите число: w\n", + "некорректное значение! ('плохое число', 'w')\n" + ] + } + ], + "source": [ + "try:\n", + " raw = input(\"введите число: \")\n", + " if not raw.isdigit():\n", + " raise ValueError(\"плохое число\", raw)\n", + " \n", + "except ValueError as err:\n", + " print(\"некорректное значение!\", err)" + ] + }, + { + "cell_type": "markdown", + "id": "531ad8cc", + "metadata": {}, + "source": [ + "В данном случае мы проверяем, что пользователь ввел не число и если так действительно произошло, то генерируем исключение и затем в блоке `except` обрабатываем его.\n", + "\n", + "Иногда может случиться так, что вы пишете какую-то функцию и отлавливаете исключения, но вы не знаете, что делать дальше с этим исключением. Например, вы можете просто вывести какую-то информацию на экран и делегировать обработку этого исключения другим функциям — тем, который вызвали вашу. Для этого нужно использовать инструкцию `raise` без параметров." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0348d576", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "введите число: e\n", + "некорректное значение! ('плохое число', 'e')\n" + ] + }, + { + "ename": "ValueError", + "evalue": "('плохое число', 'e')", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0mraw\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0minput\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"введите число: \"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0misdigit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"плохое число\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mValueError\u001b[0m: ('плохое число', 'e')" + ] + } + ], + "source": [ + "try:\n", + " raw = input(\"введите число: \")\n", + " \n", + " if not raw.isdigit():\n", + " raise ValueError(\"плохое число\", raw)\n", + "\n", + "except ValueError as err:\n", + " print(\"некорректное значение!\", err)\n", + " # делегирование обработки исключения\n", + " raise" + ] + }, + { + "cell_type": "markdown", + "id": "75611c95", + "metadata": {}, + "source": [ + "В данном случае все происходит тоже самое, как в предыдущем примере, генерируются исключения, на экран выводится надпись \"Некорректное значение\", и при помощи инструкции `raise` мы делегируем исключение выше. Если мы попробуем исполнить код, то интерпретатор просто покажет на стандартный вывод информации об этом исключении и прекратит работу нашей программы. Если таких мест, в которых исключения делегируются, очень много, то иногда не понятно по последнему исключению, где именно оно возникло и для этого может использоваться конструкция `raise from Exception`. Давайте посмотрим, как отработает наша программа." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "fea7bab6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "введите число: d\n", + "ошибка: плохое число d\n" + ] + }, + { + "ename": "TypeError", + "evalue": "ошибка", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0misdigit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"плохое число\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mValueError\u001b[0m: ('плохое число', 'd')", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"ошибка:\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 9\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"ошибка\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m: ошибка" + ] + } + ], + "source": [ + "try:\n", + " raw = input(\"введите число: \")\n", + " \n", + " if not raw.isdigit():\n", + " raise ValueError(\"плохое число\", raw)\n", + " \n", + "except ValueError as err:\n", + " print(\"ошибка:\", err.args[0], err.args[1])\n", + " raise TypeError(\"ошибка\") from err" + ] + }, + { + "cell_type": "markdown", + "id": "00db7b28", + "metadata": {}, + "source": [ + "Если мы сгенерируем одно исключение, отловим его в блоке `except`, и затем сгенерируем второе исключение `TypeError`, но укажем ему конструкцию `from err`. У нас получилось вызвать исключение и давайте внимательно посмотрим на `stack trace`, который произошел. Мы видим, что произошло исключение `ValueError` — это изначально которое мы сгенерировали. И дальше мы видим его `stack trace`, этого исключения. Дальше мы видим, что после этого исключения произошло второе исключение — `TypeError` и его `stack trace`. Таким образом, если мы будем использовать конструкцию `raise from Exception`, мы сможем отслеживать всю цепочку исключений, которая была сгенерирована." + ] + }, + { + "cell_type": "markdown", + "id": "c41f831b", + "metadata": {}, + "source": [ + "Говоря об исключениях, нельзя не затронуть инструкцию `assert`. Давайте поговорим, зачем она нужна. По умолчанию, если выполнить инструкцию `assert` с логическим выражением `True`, ничего не произойдет, но если попробовать выполнить инструкцию `assert` с логическим выражением, которое равно `False`, или \"ложь\", то будет сгенерировано исключение `AssertionError`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "64bc3761", + "metadata": {}, + "outputs": [], + "source": [ + "assert True" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4c4c07fe", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAssertionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32massert\u001b[0m \u001b[1;36m1\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mAssertionError\u001b[0m: " + ] + } + ], + "source": [ + "assert 1 == 0" + ] + }, + { + "cell_type": "markdown", + "id": "caeb02ab", + "metadata": {}, + "source": [ + "Также мы можем передать некую дополнительную строку, которая будет потом передана в сам объект `AssertionError`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "381676b1", + "metadata": {}, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "1 не равен 0", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAssertionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32massert\u001b[0m \u001b[1;36m1\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"1 не равен 0\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mAssertionError\u001b[0m: 1 не равен 0" + ] + } + ], + "source": [ + "assert 1 == 0, \"1 не равен 0\"" + ] + }, + { + "cell_type": "markdown", + "id": "e799aaab", + "metadata": {}, + "source": [ + "Давайте разберем кейсы, в каких случаях может использоваться `AssertionError` и инструкция `assert`. \n", + "\n", + "Предположим, у нас есть функция, она называется `get_user_by_id`, она нам ищет некоего пользователя по численному идентификатору. Мы должны убедиться в том, что нам действительно передали число, что мы работаем с целым числом. Это можно сделать при помощи `assert` и функции `isinstance`, то есть мы проверяем, является ли входной параметр `id` целым числом. Если это не так, то будет сгенерирована `AssertionError`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "33871e62", + "metadata": {}, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "id должен быть целым числом", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAssertionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m\"__main__\"\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[0mget_user_by_id\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"foo\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m\u001b[0m in \u001b[0;36mget_user_by_id\u001b[1;34m(id)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_user_by_id\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mid\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[1;32massert\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mid\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mint\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"id должен быть целым числом\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"выполняем поиск\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m\"__main__\"\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mAssertionError\u001b[0m: id должен быть целым числом" + ] + } + ], + "source": [ + "def get_user_by_id(id):\n", + " assert isinstance(id, int), \"id должен быть целым числом\"\n", + " print(\"выполняем поиск\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " get_user_by_id(\"foo\")" + ] + }, + { + "cell_type": "markdown", + "id": "5196f5e1", + "metadata": {}, + "source": [ + "Итак, у нас сгенерировалось исключение `AssertionError`. Дело в том, что такие исключения не нужно обрабатывать — эти исключения, они предназначены, скорее, для программистов. То есть при написании наших программ на этапе разработки мы должны видеть, что мы делаем что-то не так, то есть мы передали в функцию некорректное значение, и видим `AssertionError` — наша программа завершила свою работу. В таком случае мы должны как-то реагировать и изменять код нашей программы. Не нужно, например, обрабатывать пользовательский ввод и пытаться обработать исключение `AssetionError` блоком `try except`.\n", + "\n", + "Если у нас таких мест будет очень много, то это затронет и производительность нашей программы. Оказывается, можно отключить все инструкции `assert` при помощи флага `−O`. Тогда `AssertionError`, инструкция не будет сгенерирована, потому что `assert` вызван не будет. Этим как раз отличаются эти исключения от обычных пользовательских исключений и исключений стандартной библиотеки. Давайте попробуем сделать это. Запустим с флагом `−O` нашу программу." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "310942f0", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "выполняем поиск\n" + ] + } + ], + "source": [ + "! python -O ex.py" + ] + }, + { + "cell_type": "markdown", + "id": "765e8f49", + "metadata": {}, + "source": [ + "Итак, мы видим, что наша функция отработала, исключение `AssertionError` не было сгенерировано.\n", + "\n", + "Давайте еще поговорим немного о производительности исключений. Несмотря на то, что механизм обработки исключений очень удобный и его очень хорошо использовать в своих программах, тем не менее этот механизм не бесплатен. \n", + "\n", + "Рассмотрим пример. У нас есть цикл, и в этом цикле мы обращаемся к несуществующему ключу словаря и каждый раз при этом обращении у нас генерируется исключение `KeyError`. Попробуем при помощи `timeit` замерить, сколько это займет — мы получим некую условную единицу, тысячу операций мы смогли выполнить." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6f59e6df", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "454 µs ± 9.97 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "my_dict = {\"foo\": 1}\n", + "\n", + "for _ in range(1000):\n", + " try:\n", + " my_dict[\"bar\"]\n", + " \n", + " except KeyError:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "469eb46f", + "metadata": {}, + "source": [ + "Если мы попробуем реализовать ту же самую задачу, но без использования исключений, например, при помощи конструкции `in`, то есть мы проверяем, что у нас ключик `bar` находится в словаре, и только в этом случае тогда мы обращаемся по ключу к этому словарю. Давайте опять попробуем посмотреть на результаты работы `timeit` для этого примера — и мы видим, что результат отличается на порядок, а иногда он может отличаться и на несколько порядков." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "4c40a7cb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "67.3 µs ± 4.65 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "my_dict = {\"foo\": 1}\n", + "\n", + "for _ in range(1000):\n", + " if \"bar\" in my_dict:\n", + " _ = my_dict[\"bar\"]" + ] + }, + { + "cell_type": "markdown", + "id": "85daa3f9", + "metadata": {}, + "source": [ + "То есть хочу обратить ваше внимание на то, что исключения, особенно если они у вас генерируются в программе очень часто, возможно следует изменить код вашей программы и сделать так, чтобы она работала без исключений, потому, что это очень сильно влияет на производительность вашей итоговой Python программы.\n", + "\n", + "Итак, мы обсудили вопросы, которые касаются исключений, поговорили про доступ к объекту исключений, о том как генерируются исключения при помощи `raise`, также обсудили специальные исключения типа `AssertionError`, поговорили о вопросах производительности и нам осталось поговорить о работе с собственными исключениями. В целом работа с собственными исключениями никак не отличается от работы с исключениями стандартной библиотеки и мы попробуем в следующей лекции разобрать работу с собственными исключениями на конкретном примере работы с библиотекой `requests`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Документация.ipynb b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Документация.ipynb new file mode 100644 index 0000000..bbba6f1 --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Документация.ipynb @@ -0,0 +1,42 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9566cce7", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "a609ca01", + "metadata": {}, + "source": [ + "- [Обработка ошибок, исключения в python](https://docs.python.org/3.6/tutorial/errors.html \"8. Errors and Exceptions\")\n", + "- [Built-in exceptions](https://docs.python.org/3/library/exceptions.html \"5. Built-in Exceptions\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Задания про классы и наследование.ipynb b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Задания про классы и наследование.ipynb new file mode 100644 index 0000000..808430b --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Задания про классы и наследование.ipynb @@ -0,0 +1,155 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0aeaec09", + "metadata": {}, + "source": [ + "# Задания про классы и наследование #" + ] + }, + { + "cell_type": "markdown", + "id": "a98c34eb", + "metadata": {}, + "source": [ + "Как правило задачи про классы носят не вычислительный характер. Обычно нужно написать классы, которые отвечают определенным интерфейсам. Насколько удобны эти интерфейсы и как сильно связаны классы между собой, определит легкость их использования в будущих программах.\n", + "\n", + "Предположим есть данные о разных автомобилях и спецтехнике. Данные представлены в виде таблицы с характеристиками. Обратите внимание на то, что некоторые колонки присущи только легковым автомобилям, например, кол-во пассажирских мест. В свою очередь только у грузовых автомобилей есть длина, ширина и высота кузова.\n", + "\n", + "| Тип (`car_type`) | Марка (`brand`) | Кол-во пассажирских мест (`passenger_seats_count`) | Фото (`photo_file_name`) | Кузов ДxШxВ, м (`body_whl`) | Грузоподъемность,Тонн (`carrying`) | Дополнительно (`extra`) |\n", + "|:----------------:|:---------------:|:--------------------------------------------------:|:------------------------:|:---------------------------:|:----------------------------------:|:-------------------------------:|\n", + "| car | Nissan xTtrail | 4 | f1.jpeg | - | 2.5 | - |\n", + "| truck | Man | - | f2.jpeg | 8x3x2.5 | 20 | - |\n", + "| car | Mazda 6 | 4 | f3.jpeg | - | 2.5 | - |\n", + "| spec_machine | Hitachi | - | f4.jpeg | - | 1.2 | Легкая техника для уборки снега |\n", + "\n", + "Вам необходимо создать свою иерархию классов для данных, которые описаны в таблице.\n", + "\n", + " - `CarBase`\n", + " - `Car(CarBase)`\n", + " - `Truck(CarBase)`\n", + " - `SpecMachine(CarBase)`\n", + "\n", + "У любого объекта есть обязательный атрибут `car_type`. Он означает тип объекта и может принимать одно из значений: `car`, `truck`, `spec_machine`.\n", + "\n", + "Также у любого объекта из иерархии есть фото в виде имени файла — обязательный атрибут `photo_file_name`.\n", + "\n", + "В базовом классе нужно реализовать метод `get_photo_file_ext` для получения расширения файла (\".png\", \".jpeg\" и т.д.) с фото. Расширение файла можно получить при помощи `os.path.splitext`.\n", + "\n", + "Для грузового автомобиля необходимо разделить характеристики кузова на отдельные составляющие `body_length`, `body_width`, `body_height`. Разделитель — латинская буква `x`. Характеристики кузова могут быть заданы в виде пустой строки, в таком случае все составляющие равны `0`. Обратите внимание на то, что характеристики кузова должны быть вещественными числами.\n", + "\n", + "Также для класса грузового автомобиля необходимо реализовать метод `get_body_volume`, возвращающий объем кузова в метрах кубических.\n", + "\n", + "Все обязательные атрибуты для объектов `Car`, `Truck` и `SpecMachine` перечислены в таблице ниже, где `1` - означает, что атрибут обязателен для объекта, `0` - атрибут должен отсутствовать.\n", + "\n", + "| - | Car | Truck | SpecMachine |\n", + "|-------------------------|:---:|:-----:|:-----------:|\n", + "| `car_type` | 1 | 1 | 1 |\n", + "| `photo_file_name` | 1 | 1 | 1 |\n", + "| `brand` | 1 | 1 | 1 |\n", + "| `carrying` | 1 | 1 | 1 |\n", + "| `passenger_seats_count` | 1 | 0 | 0 |\n", + "| `body_width` | 0 | 1 | 0 |\n", + "| `body_height` | 0 | 1 | 0 |\n", + "| `body_length` | 0 | 1 | 0 |\n", + "| `extra` | 0 | 0 | 1 |\n", + "\n", + "Далее необходимо реализовать функцию, на вход которой подается имя файла в формате **csv**. Файл содержит данные аналогичны строкам из таблицы. Вам необходимо прочитать этот файл построчно при помощи модуля стандартной библиотеки **csv**. Затем проанализировать строки и создать список нужных объектов с автомобилями и специальной техникой. Функция должна возвращать список объектов.\n", + "\n", + "Не важно как вы назовете свои классы, главное чтобы их атрибуты имели имена:\n", + "\n", + "```python\n", + "car_type\n", + "brand\n", + "passenger_seats_count\n", + "photo_file_name\n", + "body_width\n", + "body_height\n", + "body_length\n", + "carrying\n", + "extra\n", + "```\n", + "\n", + "И методы:\n", + "\n", + "`get_photo_file_ext` и `get_body_volume`\n", + "\n", + "У каждого объекта из иерархии должен быть свой набор атрибутов и методов. У класса легковой автомобиль не должно быть метода `get_body_volume` в отличие от класса грузового автомобиля.\n", + "\n", + "Функция, которая парсит строки входного массива, должна называться `get_car_list`. Для проверки работы своей реализации функции `get_car_list` и всех созданных классов вам необходимо использовать следующий csv-файл: `cars.csv`.\n", + "\n", + "Первая строка в исходном файле — это заголовок **csv**, который содержит имена колонок. Нужно пропустить первую строку из исходного файла. Обратите внимание на то, что исходный файл с данными содержит некорректные строки, которые нужно пропустить. Если возникают исключения в процессе создания объектов из строк **csv**-файла, то требуется их корректно обработать стандартным способом. Проверьте работу вашего кода с входным файлом.\n", + "\n", + "Пример чтения **csv** файла:\n", + "\n", + "```python\n", + "import csv\n", + "\n", + "with open(csv_filename) as csv_fd:\n", + " reader = csv.reader(csv_fd, delimiter=';')\n", + " next(reader) # пропускаем заголовок\n", + " for row in reader:\n", + " print(row)\n", + "```\n", + "\n", + "Также обратите внимание, что все значения в **csv** файле при чтении будут python-строками. Нужно преобразовать строку в `int` для `passenger_seats_count`, во `float` для carrying, а также во `float` для `body_width` `body_height`, `body_length`.\n", + "\n", + "Также ваша программа должна быть готова к тому, что в некоторых строках данные могут быть заполнены некорректно. Например, число колонок меньше . В таком случае нужно проигнорировать подобные строки и не создавать объекты. Строки с пустым значением для `body_whl` игнорироваться не должны. Вы можете использовать механизм исключений для обработки ошибок.\n", + "\n", + "Ниже приведен пример с заготовкой кода для выполнения задания.\n", + "\n", + "```python\n", + "class CarBase:\n", + " def __init__(self, brand, photo_file_name, carrying):\n", + " pass\n", + "\n", + "\n", + "class Car(CarBase):\n", + " def __init__(self, brand, photo_file_name, carrying, passenger_seats_count):\n", + " pass\n", + "\n", + "\n", + "class Truck(CarBase):\n", + " def __init__(self, brand, photo_file_name, carrying, body_whl):\n", + " pass\n", + "\n", + "\n", + "class SpecMachine(CarBase):\n", + " def __init__(self, brand, photo_file_name, carrying, extra):\n", + " pass\n", + "\n", + "\n", + "def get_car_list(csv_filename):\n", + " car_list = []\n", + " return car_list\n", + "```\n", + "\n", + "Вам необходимо расширить функционал исходных классов, дополнить методы нужным кодом и реализовать функцию `get_car_list`.\n", + "\n", + "Успехов!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Задания про классы и наследование.pdf b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Задания про классы и наследование.pdf new file mode 100644 index 0000000..55346c9 Binary files /dev/null and b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Задания про классы и наследование.pdf differ diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Исключения в requests.ipynb b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Исключения в requests.ipynb new file mode 100644 index 0000000..697394f --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Исключения в requests.ipynb @@ -0,0 +1,534 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "06e0f3f7", + "metadata": {}, + "source": [ + "# Исключения в requests #" + ] + }, + { + "cell_type": "markdown", + "id": "ced6c366", + "metadata": {}, + "source": [ + "На предыдущих лекциях, когда мы говорили об исключениях, мы в основном использовали примеры работы с исключениями стандартной библиотеки Python. Однако существуют так называемые пользовательские исключения, и сегодня мы разберем пример работы с пользовательскими исключениями. И посмотрим, как они устроены и объявлены в библиотеке `requests`.\n", + "\n", + "Нам необходимо написать программу, которая на вход получает некий адрес в Интернете, выполняет скачивание содержимой странички по этому адресу и выводит на стандартный вывод содержимое этой странички." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c7bc44ff", + "metadata": {}, + "outputs": [ + { + "ename": "ConnectionError", + "evalue": "HTTPSConnectionPool(host='github-not-found.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11004] getaddrinfo failed'))", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mgaierror\u001b[0m Traceback (most recent call last)", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\urllib3\\connection.py\u001b[0m in \u001b[0;36m_new_conn\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 168\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 169\u001b[1;33m conn = connection.create_connection(\n\u001b[0m\u001b[0;32m 170\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_dns_host\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mport\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mextra_kw\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\urllib3\\util\\connection.py\u001b[0m in \u001b[0;36mcreate_connection\u001b[1;34m(address, timeout, source_address, socket_options)\u001b[0m\n\u001b[0;32m 72\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 73\u001b[1;33m \u001b[1;32mfor\u001b[0m \u001b[0mres\u001b[0m \u001b[1;32min\u001b[0m \u001b[0msocket\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mgetaddrinfo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mhost\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mport\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mfamily\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msocket\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mSOCK_STREAM\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 74\u001b[0m \u001b[0maf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msocktype\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mproto\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcanonname\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msa\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mres\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\socket.py\u001b[0m in \u001b[0;36mgetaddrinfo\u001b[1;34m(host, port, family, type, proto, flags)\u001b[0m\n\u001b[0;32m 917\u001b[0m \u001b[0maddrlist\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 918\u001b[1;33m \u001b[1;32mfor\u001b[0m \u001b[0mres\u001b[0m \u001b[1;32min\u001b[0m \u001b[0m_socket\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mgetaddrinfo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mhost\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mport\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mfamily\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtype\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mproto\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 919\u001b[0m \u001b[0maf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msocktype\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mproto\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcanonname\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msa\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mres\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mgaierror\u001b[0m: [Errno 11004] getaddrinfo failed", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[1;31mNewConnectionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\urllib3\\connectionpool.py\u001b[0m in \u001b[0;36murlopen\u001b[1;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001b[0m\n\u001b[0;32m 698\u001b[0m \u001b[1;31m# Make the request on the httplib connection object.\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 699\u001b[1;33m httplib_response = self._make_request(\n\u001b[0m\u001b[0;32m 700\u001b[0m \u001b[0mconn\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\urllib3\\connectionpool.py\u001b[0m in \u001b[0;36m_make_request\u001b[1;34m(self, conn, method, url, timeout, chunked, **httplib_request_kw)\u001b[0m\n\u001b[0;32m 381\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 382\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_validate_conn\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 383\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mSocketTimeout\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mBaseSSLError\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\urllib3\\connectionpool.py\u001b[0m in \u001b[0;36m_validate_conn\u001b[1;34m(self, conn)\u001b[0m\n\u001b[0;32m 1009\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mgetattr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"sock\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;31m# AppEngine might not have `.sock`\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1010\u001b[1;33m \u001b[0mconn\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mconnect\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1011\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\urllib3\\connection.py\u001b[0m in \u001b[0;36mconnect\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 352\u001b[0m \u001b[1;31m# Add certificate verification\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 353\u001b[1;33m \u001b[0mconn\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_new_conn\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 354\u001b[0m \u001b[0mhostname\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhost\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\urllib3\\connection.py\u001b[0m in \u001b[0;36m_new_conn\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 180\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mSocketError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 181\u001b[1;33m raise NewConnectionError(\n\u001b[0m\u001b[0;32m 182\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"Failed to establish a new connection: %s\"\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mNewConnectionError\u001b[0m: : Failed to establish a new connection: [Errno 11004] getaddrinfo failed", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[1;31mMaxRetryError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\\adapters.py\u001b[0m in \u001b[0;36msend\u001b[1;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[0;32m 438\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mchunked\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 439\u001b[1;33m resp = conn.urlopen(\n\u001b[0m\u001b[0;32m 440\u001b[0m \u001b[0mmethod\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mrequest\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmethod\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\urllib3\\connectionpool.py\u001b[0m in \u001b[0;36murlopen\u001b[1;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001b[0m\n\u001b[0;32m 754\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 755\u001b[1;33m retries = retries.increment(\n\u001b[0m\u001b[0;32m 756\u001b[0m \u001b[0mmethod\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0murl\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0me\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0m_pool\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0m_stacktrace\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0msys\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexc_info\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\urllib3\\util\\retry.py\u001b[0m in \u001b[0;36mincrement\u001b[1;34m(self, method, url, response, error, _pool, _stacktrace)\u001b[0m\n\u001b[0;32m 573\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mnew_retry\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mis_exhausted\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 574\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mMaxRetryError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0m_pool\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0murl\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0merror\u001b[0m \u001b[1;32mor\u001b[0m \u001b[0mResponseError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcause\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 575\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mMaxRetryError\u001b[0m: HTTPSConnectionPool(host='github-not-found.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11004] getaddrinfo failed'))", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[1;31mConnectionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mrequests\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mresponse\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mrequests\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"https://github-not-found.com\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\\api.py\u001b[0m in \u001b[0;36mget\u001b[1;34m(url, params, **kwargs)\u001b[0m\n\u001b[0;32m 74\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 75\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msetdefault\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'allow_redirects'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 76\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mrequest\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'get'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0murl\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mparams\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mparams\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 77\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 78\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\\api.py\u001b[0m in \u001b[0;36mrequest\u001b[1;34m(method, url, **kwargs)\u001b[0m\n\u001b[0;32m 59\u001b[0m \u001b[1;31m# cases, and look like a memory leak in others.\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 60\u001b[0m \u001b[1;32mwith\u001b[0m \u001b[0msessions\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mSession\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0msession\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 61\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0msession\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrequest\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmethod\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mmethod\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0murl\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0murl\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 62\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 63\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\\sessions.py\u001b[0m in \u001b[0;36mrequest\u001b[1;34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001b[0m\n\u001b[0;32m 540\u001b[0m }\n\u001b[0;32m 541\u001b[0m \u001b[0msend_kwargs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0msettings\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 542\u001b[1;33m \u001b[0mresp\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mprep\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0msend_kwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 543\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 544\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mresp\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\\sessions.py\u001b[0m in \u001b[0;36msend\u001b[1;34m(self, request, **kwargs)\u001b[0m\n\u001b[0;32m 653\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 654\u001b[0m \u001b[1;31m# Send the request\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 655\u001b[1;33m \u001b[0mr\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0madapter\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrequest\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 656\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 657\u001b[0m \u001b[1;31m# Total elapsed time of the request (approximately)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\\adapters.py\u001b[0m in \u001b[0;36msend\u001b[1;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[0;32m 514\u001b[0m \u001b[1;32mraise\u001b[0m \u001b[0mSSLError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0me\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mrequest\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mrequest\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 515\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 516\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mConnectionError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0me\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mrequest\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mrequest\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 517\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 518\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mClosedPoolError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mConnectionError\u001b[0m: HTTPSConnectionPool(host='github-not-found.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 11004] getaddrinfo failed'))" + ] + } + ], + "source": [ + "import requests\n", + "\n", + "response = requests.get(\"https://github-not-found.com\")" + ] + }, + { + "cell_type": "markdown", + "id": "01eea10e", + "metadata": {}, + "source": [ + "Эта программа должна обрабатывать все исключения, которые возникают в процессе ее работы, и обрабатывать эти исключения соответствующим образом.\n", + "\n", + "Давайте для начала посмотрим, какие исключения существуют в библиотеке `requests`. Я уже установил виртуальное окружение, установил саму библиотеку `requests`. Вы уже знаете, как это делать. Давайте запустим интерпретатор, выполним импорт библиотеки, и посмотрим, где она находится в нашей системе." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "44e3584a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "C:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\\__init__.py\n" + ] + } + ], + "source": [ + "import inspect\n", + "import requests\n", + "\n", + "\n", + "print(inspect.getfile(requests))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "eb3b69be", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "C:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\\__init__.py\n" + ] + } + ], + "source": [ + "print(requests.__file__)" + ] + }, + { + "cell_type": "markdown", + "id": "2615aff2", + "metadata": {}, + "source": [ + "Итак, у нас получилось. Давайте скопируем в буфер обмена этот путь. И обратимся к специальному файлу, который называется `exceptions.py`. На экране мы видим содержимое файла `exceptions.py`, библиотеки `requests`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8dcc499a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ’®¬ ў гбва®©б⢥ C ­Ґ Ё¬ҐҐв ¬ҐвЄЁ.\n", + " ‘ҐаЁ©­л© ­®¬Ґа ⮬ : 581B-1E7B\n", + "\n", + " ‘®¤Ґа¦Ё¬®Ґ Ї ЇЄЁ C:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\n", + "\n", + "31.05.2021 16:54 .\n", + "31.05.2021 16:54 ..\n", + "18.12.2020 02:44 21я344 adapters.py\n", + "18.12.2020 02:44 6я496 api.py\n", + "18.12.2020 02:44 10я207 auth.py\n", + "18.12.2020 02:44 453 certs.py\n", + "18.12.2020 02:44 1я782 compat.py\n", + "18.12.2020 02:44 18я430 cookies.py\n", + "18.12.2020 02:44 3я161 exceptions.py\n", + "18.12.2020 02:44 3я515 help.py\n", + "18.12.2020 02:44 757 hooks.py\n", + "18.12.2020 02:44 34я308 models.py\n", + "18.12.2020 02:44 542 packages.py\n", + "18.12.2020 02:44 30я137 sessions.py\n", + "18.12.2020 02:44 4я188 status_codes.py\n", + "18.12.2020 02:44 3я005 structures.py\n", + "18.12.2020 02:44 30я529 utils.py\n", + "18.12.2020 02:44 1я096 _internal_utils.py\n", + "18.12.2020 02:44 4я141 __init__.py\n", + "31.05.2021 16:56 __pycache__\n", + "18.12.2020 02:44 441 __version__.py\n", + " 18 д ©«®ў 174я532 Ў ©в\n", + " 3 Ї Ї®Є 42я176я192я512 Ў ©в бў®Ў®¤­®\n" + ] + } + ], + "source": [ + "! dir C:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3695b64f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# -*- coding: utf-8 -*-\n", + "\n", + "\"\"\"\n", + "requests.exceptions\n", + "~~~~~~~~~~~~~~~~~~~\n", + "\n", + "This module contains the set of Requests' exceptions.\n", + "\"\"\"\n", + "from urllib3.exceptions import HTTPError as BaseHTTPError\n", + "\n", + "\n", + "class RequestException(IOError):\n", + " \"\"\"There was an ambiguous exception that occurred while handling your\n", + " request.\n", + " \"\"\"\n", + "\n", + " def __init__(self, *args, **kwargs):\n", + " \"\"\"Initialize RequestException with `request` and `response` objects.\"\"\"\n", + " response = kwargs.pop('response', None)\n", + " self.response = response\n", + " self.request = kwargs.pop('request', None)\n", + " if (response is not None and not self.request and\n", + " hasattr(response, 'request')):\n", + " self.request = self.response.request\n", + " super(RequestException, self).__init__(*args, **kwargs)\n", + "\n", + "\n", + "class HTTPError(RequestException):\n", + " \"\"\"An HTTP error occurred.\"\"\"\n", + "\n", + "\n", + "class ConnectionError(RequestException):\n", + " \"\"\"A Connection error occurred.\"\"\"\n", + "\n", + "\n", + "class ProxyError(ConnectionError):\n", + " \"\"\"A proxy error occurred.\"\"\"\n", + "\n", + "\n", + "class SSLError(ConnectionError):\n", + " \"\"\"An SSL error occurred.\"\"\"\n", + "\n", + "\n", + "class Timeout(RequestException):\n", + " \"\"\"The request timed out.\n", + "\n", + " Catching this error will catch both\n", + " :exc:`~requests.exceptions.ConnectTimeout` and\n", + " :exc:`~requests.exceptions.ReadTimeout` errors.\n", + " \"\"\"\n", + "\n", + "\n", + "class ConnectTimeout(ConnectionError, Timeout):\n", + " \"\"\"The request timed out while trying to connect to the remote server.\n", + "\n", + " Requests that produced this error are safe to retry.\n", + " \"\"\"\n", + "\n", + "\n", + "class ReadTimeout(Timeout):\n", + " \"\"\"The server did not send any data in the allotted amount of time.\"\"\"\n", + "\n", + "\n", + "class URLRequired(RequestException):\n", + " \"\"\"A valid URL is required to make a request.\"\"\"\n", + "\n", + "\n", + "class TooManyRedirects(RequestException):\n", + " \"\"\"Too many redirects.\"\"\"\n", + "\n", + "\n", + "class MissingSchema(RequestException, ValueError):\n", + " \"\"\"The URL schema (e.g. http or https) is missing.\"\"\"\n", + "\n", + "\n", + "class InvalidSchema(RequestException, ValueError):\n", + " \"\"\"See defaults.py for valid schemas.\"\"\"\n", + "\n", + "\n", + "class InvalidURL(RequestException, ValueError):\n", + " \"\"\"The URL provided was somehow invalid.\"\"\"\n", + "\n", + "\n", + "class InvalidHeader(RequestException, ValueError):\n", + " \"\"\"The header value provided was somehow invalid.\"\"\"\n", + "\n", + "\n", + "class InvalidProxyURL(InvalidURL):\n", + " \"\"\"The proxy URL provided is invalid.\"\"\"\n", + "\n", + "\n", + "class ChunkedEncodingError(RequestException):\n", + " \"\"\"The server declared chunked encoding but sent an invalid chunk.\"\"\"\n", + "\n", + "\n", + "class ContentDecodingError(RequestException, BaseHTTPError):\n", + " \"\"\"Failed to decode response content.\"\"\"\n", + "\n", + "\n", + "class StreamConsumedError(RequestException, TypeError):\n", + " \"\"\"The content for this response was already consumed.\"\"\"\n", + "\n", + "\n", + "class RetryError(RequestException):\n", + " \"\"\"Custom retries logic failed\"\"\"\n", + "\n", + "\n", + "class UnrewindableBodyError(RequestException):\n", + " \"\"\"Requests encountered an error when trying to rewind a body.\"\"\"\n", + "\n", + "# Warnings\n", + "\n", + "\n", + "class RequestsWarning(Warning):\n", + " \"\"\"Base warning for Requests.\"\"\"\n", + "\n", + "\n", + "class FileModeWarning(RequestsWarning, DeprecationWarning):\n", + " \"\"\"A file was opened in text mode, but Requests determined its binary length.\"\"\"\n", + "\n", + "\n", + "class RequestsDependencyWarning(RequestsWarning):\n", + " \"\"\"An imported dependency doesn't match the expected version range.\"\"\"\n" + ] + } + ], + "source": [ + "! type C:\\ProgramData\\Anaconda3\\lib\\site-packages\\requests\\exceptions.py" + ] + }, + { + "cell_type": "markdown", + "id": "770b2e7a", + "metadata": {}, + "source": [ + "Давайте обратим внимание на исключения, которые в нем перечислены. Исключение `RequestException` является базовым классом для всех остальных исключений. В целом мы можем отлавливать именно это исключение и реагировать на ошибку работы этой библиотеки. Также существуют и другие исключения. Например, исключение `HTTPError`. Оно может генерироваться в случае, если нам пришел ответ, и его статус не равен `200 ОК`. Также различные исключения вида `ConnectionError` или `TimeoutError` могут возникать при проблемах с соединением по хосту, который указан в адресе.\n", + "\n", + "Итак, давайте начнем писать саму программу. Нам понадобится модуль `sys`, импортируем его. Также нам понадобится библиотека `requests`. Давайте получим адрес при помощи библиотеки `sys`. И попробуем скачать содержимое по этому адресу. После того, как мы скачали ответ, мы можем напечатать его. Давайте попробуем выполнить нашу программу." + ] + }, + { + "cell_type": "markdown", + "id": "b074f2ac", + "metadata": {}, + "source": [ + "```python\n", + "import sys\n", + "import requests\n", + "\n", + "url = sys.argv[1]\n", + "\n", + "response = requests.get(url)\n", + "\n", + "print(response.content)\n", + "```\n", + "\n", + "Запускаем ее при помощи интерпретатора, указываем адрес. Пусть это будет `python.net`. Итак, у нас получилось скачать содержимое по этому адресу и вывести на экран." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c466a76c", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "b'\\n\\n\\n\\n \\n\\n Jupyter Notebook\\n \\n \\n \\n \\n \\n \\n \\n\\n \\n \\n\\n\\n \\n \\n \\n \\n \\n \\n \\n\\n \\n \\n\\n\\n\\n\\n\\n\\n\\n
\\n
\\n \\n\\n \\n \\n \\n \\n \\n \\n\\n\\n \\n \\n
\\n
\\n\\n \\n \\n
\\n\\n
\\n\\n\\n
\\n\\n \\n \\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n \\n
\\n

\\n Token authentication is enabled\\n

\\n

\\n If no password has been configured, you need to open the notebook\\n server with its login token in the URL, or paste it above.\\n This requirement will be lifted if you\\n \\n enable a password.\\n

\\n

\\n The command:\\n

jupyter notebook list
\\n will show you the URLs of running servers with their tokens,\\n which you can copy and paste into your browser. For example:\\n

\\n
Currently running servers:\\nhttp://localhost:8888/?token=c8de56fa... :: /Users/you/notebooks\\n
\\n

\\n or you can paste just the token value into the password field on this\\n page.\\n

\\n

\\n See\\n \\n the documentation on how to enable a password\\n \\n in place of token authentication,\\n if you would like to avoid dealing with random tokens.\\n

\\n

\\n Cookies are required for authenticated access to notebooks.\\n

\\n \\n

Setup a Password

\\n

You can also setup a password by entering your token and a new password\\n on the fields below:

\\n
\\n \\n
\\n \\n \\n
\\n
\\n \\n \\n
\\n
\\n \\n
\\n
\\n \\n\\n
\\n \\n \\n
\\n\\n\\n
\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n'" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "! python wget.py http://localhost:8888" + ] + }, + { + "cell_type": "markdown", + "id": "e67e676e", + "metadata": {}, + "source": [ + "Давайте теперь напишем обработчики исключений, которые могут возникать при работе с данной библиотекой.\n", + "\n", + "Во-первых, давайте зададим `timeout`. Пусть он будет равен 30-ти секундам. Если мы задаем `timeout`, и за эти 30 секунд мы не получаем ответа от сервера, то будет сгенерировано исключение `TimeoutError`. Давайте его обработаем. Для этого напишем `try/except`, `requests.Timeout`. Если мы получили `Timeout`, выведем соответствующее сообщение. И укажем `url`. Если же у нас исключение не произошло, мы можем использовать блок `else`. И в блоке `else` выводить содержимое нашего ответа.\n", + "\n", + "```python\n", + "import sys\n", + "import requests\n", + "\n", + "url = sys.argv[1]\n", + "\n", + "try:\n", + " response = requests.get(url, timeout=30)\n", + " \n", + "except requests.Timeout:\n", + " print(\"Ошибка timeout, url:\", url)\n", + "\n", + "else:\n", + " print(response.content)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "e95fa508", + "metadata": {}, + "source": [ + "Но допустим, если мы ввели несуществующий адрес, и статус ответа не 200 ОК, нам тоже необходимо обработать такую ошибку. Сделать это можно следующим образом. Для начала необходимо сгенерировать исключение `HTTPError`. Делается это при помощи метода `raise_for_status`. И затем мы сможем отловить это исключение. Давайте выведем соответствующее сообщение об ошибке. Предположим, мы хотим также в сообщении об ошибке видеть `status code` нашего ответа. Можем сделать это. И сейчас я покажу, как это можно сделать. Если у нас произошло исключение вида `HTTPError`, то сам ответ доступен по атрибуту `response`. И мы можем обратиться к нему соответствующим образом. Нам нужен сам `status code`, он хранится в атрибуте `status_code`. Всё, теперь мы можем вывести его.\n", + "\n", + "```python\n", + "import sys\n", + "import requests\n", + "\n", + "url = sys.argv[1]\n", + "\n", + "try:\n", + " response = requests.get(url, timeout=30)\n", + " response.raise_for_status()\n", + " \n", + "except requests.Timeout:\n", + " print(\"Ошибка timeout, url:\", url)\n", + "\n", + "except requests.HTTPError as err:\n", + " code = err.response.status_code\n", + " print(f\"Ошибка url: {url}, code: {code}\")\n", + " \n", + "else:\n", + " print(response.content)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "4e250678", + "metadata": {}, + "source": [ + "Также у нас могут возникать и другие исключения. Давайте обработаем и этот момент. Мы будем отлавливать базовый класс и выводить соответствующее сообщение. Вот так выглядит обработка исключений от библиотеки `requests`. Давайте попробуем запустить нашу программу. И посмотрим, как она работает. Давайте, например, обратимся к несуществующему адресу. Запускаем.\n", + "\n", + "```python\n", + "import sys\n", + "import requests\n", + "\n", + "url = sys.argv[1]\n", + "\n", + "try:\n", + " response = requests.get(url, timeout=30)\n", + " response.raise_for_status()\n", + " \n", + "except requests.Timeout:\n", + " print(\"Ошибка timeout, url:\", url)\n", + "\n", + "except requests.HTTPError as err:\n", + " code = err.response.status_code\n", + " print(f\"Ошибка url: {url}, code: {code}\")\n", + "\n", + "except requests.RequestException:\n", + " print(\"Ошибка скачивания url:\", url)\n", + " \n", + "else:\n", + " print(response.content)\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "8660cd85", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ошибка url: http://localhost:8888/test, code: 404\n" + ] + } + ], + "source": [ + "! python wget.py http://localhost:8888/test" + ] + }, + { + "cell_type": "markdown", + "id": "37e905f6", + "metadata": {}, + "source": [ + "Так видим соответствующее сообщение в нашем стандартном выводе. Также мы можем сгенерировать какое-то другое исключение. Давайте попробуем обратиться к несуществующему хосту." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "88c232ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ошибка скачивания url: https://google.com\n" + ] + } + ], + "source": [ + "! python wget.py https://google.com" + ] + }, + { + "cell_type": "markdown", + "id": "8ed15ace", + "metadata": {}, + "source": [ + "Итак, видим, что у нас соответствующее исключение было обработано нужным образом. Таким образом, мы написали программу, которая скачивает `url` и обрабатывает все нужные исключения, которые могут возникнуть при работе с библиотекой `requests`.\n", + "\n", + "Вы можете писать собственные исключения и делать это похожим образом, как это реализовано в этой библиотеке. Мы с вами поговорили про исключения. Про то, как устроены пользовательские исключения. И теперь вы можете использовать этот механизм для написания заданий по нашему курсу." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Классы исключений и их обработка.ipynb b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Классы исключений и их обработка.ipynb new file mode 100644 index 0000000..ac55dd3 --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Классы исключений и их обработка.ipynb @@ -0,0 +1,741 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "adeac7e8", + "metadata": {}, + "source": [ + "# Классы исключений и их обработка #" + ] + }, + { + "cell_type": "markdown", + "id": "e85bd50b", + "metadata": {}, + "source": [ + "В предыдущих лекциях мы несколько раз упоминали про исключения в Python. Сегодня мы как раз обсудим то, как устроены исключения в Python. Мы поговорим про генерацию исключений, что при этом происходит, обсудим типы исключений, а также рассмотрим, как обрабатывать исключения в Python.\n", + "\n", + "Для начала давайте попробуем вызвать исключения и посмотрим, что при этом произойдет. Для этого нам потребуется консоль. Давайте попробуем написать простенькую программу на Python. Пусть это будет файл `zero.py`. Попробуем вывести строчку `1/0`. Давайте запустим нашу программу." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3a0fab6c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Traceback (most recent call last):\n", + " File \"zero.py\", line 1, in \n", + " 1/0\n", + "ZeroDivisionError: division by zero\n" + ] + } + ], + "source": [ + "! python zero.py" + ] + }, + { + "cell_type": "markdown", + "id": "546ec448", + "metadata": {}, + "source": [ + "Итак, мы видим, что произошло. При делении на 0 возникло исключение, и при возникновении исключения на стандартный вывод печатается информация о его типе, дополнительная информация о исключении, а также стек вызовов.\n", + "\n", + "Давайте посмотрим, что у нас произошло. Мы видим, на экран напечаталось - тип исключения — `ZeroDivisionError`, дополнительная информация и сам стек. Стек у нас пока небольшой, и для того чтобы посмотреть на стек настоящей программы, давайте посмотрим дополнительный пример." + ] + }, + { + "cell_type": "markdown", + "id": "4e1a1ee8", + "metadata": {}, + "source": [ + "Пусть у нас есть программа, которая считает количество строк в исходном файле, который подали на вход. Выглядит она нам не очень интересно как, то есть есть некая функция `main`, в функции `main` вызывается другая функция. Эта функция вызывает функцию `wc` с переданным файлом и печатает на экран некую строчку с именем файла и количеством строк в нем. А функция `wc` открывает этот файл и итератором проходится по всем строкам в этом файле.\n", + "\n", + "```python\n", + "import sys\n", + "\n", + "def wc(filename):\n", + " count = 0\n", + " \n", + " with open(filename) as f:\n", + " for _ in f:\n", + " count += 1\n", + " \n", + " return count\n", + "\n", + "def process_file(filename):\n", + " count = wc(filename)\n", + " \n", + " print(f\"file: {filename} has {count} lines\")\n", + "\n", + "def _main():\n", + " process_file(sys.argv[1])\n", + " \n", + "if __name__ == \"__main__\":\n", + " _main()\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8f7658a8", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Traceback (most recent call last):\n", + " File \"wc.py\", line 21, in \n", + " _main()\n", + " File \"wc.py\", line 18, in _main\n", + " process_file(sys.argv[1])\n", + " File \"wc.py\", line 13, in process_file\n", + " count = wc(filename)\n", + " File \"wc.py\", line 6, in wc\n", + " with open(filename) as f:\n", + "FileNotFoundError: [Errno 2] No such file or directory: '/etc/test'\n" + ] + } + ], + "source": [ + "! python wc.py /etc/test" + ] + }, + { + "cell_type": "markdown", + "id": "7e6043d2", + "metadata": {}, + "source": [ + "Давайте попробуем вызвать данную программу, передадим ей либо несуществующий файл, либо файл, недоступный для чтения, и посмотрим, что при этом произойдет. Итак, у нас есть наша программа. Давайте попробуем ей передать файл, которого нет. Это файл `/etc/test`. Итак, как видим, сгенерировалось исключение.\n", + "\n", + "При этом Python остановил свою работу, и на экране мы видим тип исключения — это `FileNotFoundError`, а также дополнительную информацию - код ошибки, текстовые сообщения, что файла `/etc/test` нет. Также теперь мы видим стек вызовов, теперь он у нас побольше. Давайте распечатаем текст самой программы и немножко разберемся, что же значит этот стек вызовов.\n", + "\n", + "Давайте посмотрим, что на строке 6 фукнции `wc` была вызвана функция `open`. Давайте еще раз сгенерируем наше исключение. Итак, мы сгенерировали исключение, и посмотрим на текст нашей программы. Итак, мы видим, что в строке 6 нашей программы была вызвана функция `open`. Именно это привело к генерации исключения. Давайте дальше пройдемся по стеку вызовов и раскрутим его наверх. Мы видим, что наша функция `wc` была вызвана на строке 13, вот видим ее. Это было сделано функцией `process_file`. Если мы будем раскручивать стек дальше, то мы сможем отследить всю последовательность вызовов функции, которая привела к исключению. Таким образом, это нам очень может сильно помочь, если мы будем разбираться с чужими исключениями, которые вдруг внезапно возникли в программе и не были обработаны программистом.\n", + "\n", + "Какие типы исключений бывают? В Python бывают по большому счету два типа исключений.\n", + "\n", + "Первый — это исключения из стандартной библиотеки в Python. Они генерируются, собственно, самой библиотекой. То есть когда мы вызываем функцию стандартной библиотеки, например, мы видели, как функция `open` сгенерировала исключение `PermissionError`.\n", + "\n", + "А также второй тип исключений — это пользовательские исключения. Они могут быть сгенерированы и обработаны самим программистом при написании программ на Python.\n", + "\n", + "Давайте посмотрим на иерархию исключений в стандартной библиотеке Python. Все исключения наследуются от базового класса `BaseException`. Существуют несколько системных исключений, например, `SystemExit` — это исключение генерируется, если мы вызвали функцию `OSExit`. `KeyboardInterrupt` — это исключение генерируется, если мы нажали сочетание клавиш `Ctrl + C` и так далее." + ] + }, + { + "cell_type": "markdown", + "id": "0ffb082b", + "metadata": {}, + "source": [ + "```\n", + "BaseException\n", + "+-- SystemExit\n", + "+-- KeyboardInterrupt\n", + "+-- GeneratorExit\n", + "+-- Exception\n", + " +-- StopIteration\n", + " +-- AssertionError\n", + " +-- AttributeError\n", + " +-- LookupError\n", + " +-- IndexError\n", + " +-- KeyError\n", + " +-- OSError\n", + " +-- SystemError\n", + " +-- TypeError\n", + " +-- ValueError\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "1dd418b8", + "metadata": {}, + "source": [ + "Все остальные исключения генерируется от базового класса `Exception`. Именно от этого класса нужно будет порождать и свои исключения, чем мы и займемся дальше.\n", + "\n", + "Давайте посмотрим и обсудим некоторые исключения из базы библиотеки, такие как, например, `AttributeError`, `IndexError`, `KeyError`, `TypeError`, и попробуем их вызвать. Для этого нам снова потребуется консоль. Давайте запустим интерпретатор. Объявим простенький класс. Пусть это будет класс, который ничего не делает." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b09aeca0", + "metadata": {}, + "outputs": [], + "source": [ + "class MyClass:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "9f2183bb", + "metadata": {}, + "source": [ + "Давайте создадим объект этого класса и попробуем обратиться к атрибуту `foo`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "6ac92428", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'MyClass' object has no attribute 'foo'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mobj\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mMyClass\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mobj\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfoo\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m: 'MyClass' object has no attribute 'foo'" + ] + } + ], + "source": [ + "obj = MyClass()\n", + "obj.foo" + ] + }, + { + "cell_type": "markdown", + "id": "e2657a83", + "metadata": {}, + "source": [ + "Как мы видим, сгенерировалось исключение `AttributeError`.\n", + "\n", + "Давайте попробуем объявить словарик. Пусть в нем будет один элемент. " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e57ffe19", + "metadata": {}, + "outputs": [], + "source": [ + "d = {\"foo\": 1}" + ] + }, + { + "cell_type": "markdown", + "id": "7535f072", + "metadata": {}, + "source": [ + "Попробуем обратиться к несуществующему ключику." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "69060ba8", + "metadata": {}, + "outputs": [ + { + "ename": "KeyError", + "evalue": "'bar'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0md\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"bar\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mKeyError\u001b[0m: 'bar'" + ] + } + ], + "source": [ + "d[\"bar\"]" + ] + }, + { + "cell_type": "markdown", + "id": "87cb9673", + "metadata": {}, + "source": [ + "Итак, у нас получилось сгенерировать исключение `KeyError`.\n", + "\n", + "Если бы мы объявили список из двух элементов и обратились, например, к 10-му элементу, у нас бы сгенерировался `IndexError`." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "e134d942", + "metadata": {}, + "outputs": [ + { + "ename": "IndexError", + "evalue": "list index out of range", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mIndexError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0ml\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0ml\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mIndexError\u001b[0m: list index out of range" + ] + } + ], + "source": [ + "l = [1, 2]\n", + "l[10]" + ] + }, + { + "cell_type": "markdown", + "id": "aeb59421", + "metadata": {}, + "source": [ + "Также если мы, например, попробовали преобразовать к целому числу строчку, мы бы получили `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "cd6a04fc", + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "invalid literal for int() with base 10: 'fdf'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"fdf\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mValueError\u001b[0m: invalid literal for int() with base 10: 'fdf'" + ] + } + ], + "source": [ + "int(\"fdf\")" + ] + }, + { + "cell_type": "markdown", + "id": "51be3abc", + "metadata": {}, + "source": [ + "Или если бы попробовали сложить число и строку, получили бы `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f6ccfd3b", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "unsupported operand type(s) for +: 'int' and 'str'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;36m1\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;34m\"1\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'int' and 'str'" + ] + } + ], + "source": [ + "1 + \"1\"" + ] + }, + { + "cell_type": "markdown", + "id": "6ea521e0", + "metadata": {}, + "source": [ + "Все эти исключения — из стандартной библиотеки. Вам при работе очень часто придется сталкиваться с ними, и теперь вы знаете, как они выглядят, и при каких обстоятельствах они могут быть сгенерированы.\n", + "\n", + "Если исключение сгенерировано, то, как я уже говорил, Python-интерпретатор остановит свою работу и на экран будет выведен стек вызовов и информация о типе исключений.\n", + "\n", + "И нам это не всегда хочется, чтобы такое поведение было по умолчанию, и поэтому нужно как-то обработать сгенерированные исключения. Обработать его можно при помощи блока `try except`. То есть у нас есть код, который потенциально может генерировать исключения, мы этот код обрамляем в блок `try except`, и тем самым при генерации исключений управление будет передано в блок `except`." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "a88352fe", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Error\n" + ] + } + ], + "source": [ + "try:\n", + " 1 / 0\n", + "\n", + "except:\n", + " print(\"Error\")" + ] + }, + { + "cell_type": "markdown", + "id": "ed95ab0c", + "metadata": {}, + "source": [ + "Таким образом можно отловить все исключения, которые генерируются в блоке `try except`.\n", + "\n", + "Если мы в блоке `except` укажем исключение, например в данном случае `Exception`, то мы будем отлавливать исключения всех типов, у которых класс `Exception` является родителем." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "c48a05fb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Error\n" + ] + } + ], + "source": [ + "try:\n", + " 1 / 0\n", + "\n", + "except Exception:\n", + " print(\"Error\")" + ] + }, + { + "cell_type": "markdown", + "id": "4427af89", + "metadata": {}, + "source": [ + "В целом неправильно ждать любые исключения, и это может привести к непредвиденным сюрпризам работы вашей программы.\n", + "\n", + "Давайте рассмотрим следующий пример. Мы в бесконечном цикле просим пользователя ввести число, преобразовываем его строку к числу целому." + ] + }, + { + "cell_type": "markdown", + "id": "b8d34e47", + "metadata": {}, + "source": [ + "```python\n", + "while True:\n", + " try:\n", + " raw = input(\"Input number: \")\n", + " number = int(raw)\n", + " break\n", + " \n", + " except:\n", + " print(\"not number\")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "0f185a81", + "metadata": {}, + "source": [ + "В данном случае может возникнуть исключение `ValueError`. Однако, мы в своей программе отлавливаем все исключения. Давайте попробуем ее исполнить и посмотрим, как она работает. Так, нас просят ввести число. Давайте введем. Нас снова просят ввести число. Давайте, например, попросим наш интерпретатор завершить свою работу и нажмем `Ctrl + C`.\n", + "\n", + "Как мы видим, у нас это не получилось сделать, то есть наша программа стала вести себя непредвиденным образом. Она нас снова опять заставляет ввести число. Это как раз говорит о том, что нужно обрабатывать конкретные исключения, и в данном случае правильным вариантом данной программы была бы обработка исключений `ValueError`." + ] + }, + { + "cell_type": "markdown", + "id": "eef3c8bd", + "metadata": {}, + "source": [ + "```python\n", + "while True:\n", + " try:\n", + " raw = input(\"Input number: \")\n", + " number = int(raw)\n", + " break\n", + " \n", + " except ValueError:\n", + " print(\"not number\")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "d7444dfb", + "metadata": {}, + "source": [ + "Если мы попробуем снова запустить данную программу и нажмем `Ctrl + C`. Давайте введем сначала неправильное число, нажмем `Ctrl + C`, то возникнет исключение и его никто не обработает, и наша программа завершит свою работу. Поэтому не следует обрабатывать все исключения и оставлять пустым блок `except`. Имейте это в виду.\n", + "\n", + "Также у блока `try except` может быть блок `else`. Блок `else` вызывается в том случае, если никакого исключения не произошло." + ] + }, + { + "cell_type": "markdown", + "id": "28cb2cbe", + "metadata": {}, + "source": [ + "```python\n", + "while True:\n", + " try:\n", + " raw = input(\"Input number: \")\n", + " number = int(raw)\n", + " break\n", + " \n", + " except ValueError:\n", + " print(\"not number\")\n", + " \n", + " else:\n", + " break\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "545ef10a", + "metadata": {}, + "source": [ + "Если нам нужно обработать несколько исключений, мы можем использовать несколько блоков `except` и указать разные классы для обработки исключения. Причем в каждом блоке `except` может быть свой собственный обработчик." + ] + }, + { + "cell_type": "markdown", + "id": "2a06fab7", + "metadata": {}, + "source": [ + "```python\n", + "while True:\n", + " try:\n", + " raw = input(\"Input number: \")\n", + " number = int(raw)\n", + " break\n", + " \n", + " except ValueError:\n", + " print(\"not number\")\n", + " \n", + " except KeyboardInterrupt:\n", + " print(\"exit\")\n", + " break\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "dd9d3b79", + "metadata": {}, + "source": [ + "Например, если мы в данном примере ввели некорректное число, то мы еще раз продолжим работу нашего цикла. В противном случае, если мы нажали `Ctrl + C`, то мы прекратим цикл. Именно таким образом можно обработать несколько исключений." + ] + }, + { + "cell_type": "markdown", + "id": "2db929d8", + "metadata": {}, + "source": [ + "Если обработчик исключений выглядит одинаково, то несколько исключений можно передать в виде списка в блок `except` и также обработать сгенерированные исключения." + ] + }, + { + "cell_type": "markdown", + "id": "8185d03b", + "metadata": {}, + "source": [ + "```python\n", + "while True:\n", + " try:\n", + " raw = input(\"Input number: \")\n", + " number = int(raw)\n", + " total_count /= number\n", + " break\n", + " \n", + " except (ValueError, ZeroDivisionError):\n", + " print(\"not number\")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "076e56ef", + "metadata": {}, + "source": [ + "В данном случае мы ожидаем два исключения, например, что пользователь ввел некорректное число, либо если он ввел 0, то данная программа сгенерирует `ZeroDivisionError`, и мы его отловим.\n", + "\n", + "Мы уже с вами обсуждали классы и наследования в классах, и вот у `exception`'ов есть своя иерархия, и сделана она неспроста. Также при помощи родительского класса можно обрабатывать несколько исключений. Давайте, например, посмотрим на иерархию классов `LookupError`." + ] + }, + { + "cell_type": "markdown", + "id": "8ce76d09", + "metadata": {}, + "source": [ + "```\n", + "+-- LookupError\n", + " +-- IndexError\n", + " +-- KeyError\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "047d3573", + "metadata": {}, + "source": [ + "Этот класс является родительским для исключений `IndexError` и `KeyError`. Мы можем это проверить при помощи известных нам функций `issubclass`. Рассмотрим следующий пример." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "42250fda", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "issubclass(KeyError, LookupError)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b891412f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "issubclass(IndexError, LookupError)" + ] + }, + { + "cell_type": "markdown", + "id": "a7b4d748", + "metadata": {}, + "source": [ + "У нас есть некая структура данных, это наша база данных, которая хранит по цветам надписи на футболках. Нам необходимо получать доступ к этим надписям по цветам.\n", + "\n", + "Пользователя просим ввести цвет, просим ввести номер по порядку, а затем обращаемся к нашей структуре данных по ключу, а затем по индексу. И если пользователь введет, например, некорректный цвет, то будет сгенерировано исключение `KeyError`, а если пользователь введет недопустимый индекс, то будет вызвано исключение `IndexError`. Все эти исключения мы можем обработать при помощи базового класса `LookupError`. Иногда это очень удобно и требуется для как раз обработки пользовательских исключений. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "be5fda81", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "введите цвет: red\n", + "введите номер по порядку: 1\n", + "вы выбрали: flower\n" + ] + } + ], + "source": [ + "database = {\n", + " \"red\": [\"fox\", \"flower\"],\n", + " \"green\": [\"peace\", \"M\", \"python\"]\n", + "}\n", + "\n", + "try:\n", + " color = input(\"введите цвет: \")\n", + " number = input(\"введите номер по порядку: \")\n", + " label = database[color][int(number)]\n", + " print(\"вы выбрали:\", label)\n", + "\n", + "except LookupError:\n", + " # except (IndexError, KeyError):\n", + " print(\"Объект не найден\")" + ] + }, + { + "cell_type": "markdown", + "id": "a0060f21", + "metadata": {}, + "source": [ + "Также у исключений есть дополнительный блок `finally`. Рассмотрим проблему. Например, мы открываем файл, читаем строки, обрабатываем как-то эти строки, и в процессе работы нашей программы возникает исключение, которое мы не ждем, например. В таком случае файл закрыт не будет. У нас эти открытые файловые дескрипторы могут накапливаться, что, в принципе, не хорошо. Таким же образом могут накапливаться открытые сокеты, или не освобождаться память, всё что угодно.\n", + "\n", + "Для контроля таких ситуаций существуют, во-первых, контекстные менеджеры, а во-вторых, можно использовать блок `finally` в исключениях. Выглядит это таким образом, как представлено на слайде." + ] + }, + { + "cell_type": "markdown", + "id": "0a0ce1a0", + "metadata": {}, + "source": [ + "```python\n", + "f = open(\"/etc/hosts\")\n", + "\n", + "try:\n", + " for line in f:\n", + " print(line.rstrip(\"\\n\"))\n", + " 1 / 0\n", + " \n", + "except OSError:\n", + " print(\"ошибка\")\n", + " \n", + "finally:\n", + " f.close()\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "991a0c2a", + "metadata": {}, + "source": [ + "Мы пишем блок `finally` и вызываем метод `close` для нашего объекта `f`. В данном случае блок `finally` будет выполнен в любом случае. Возникло исключение или не возникло — блок `finally` будет выполнен.\n", + "\n", + "Итак, мы поговорили с вами про исключения, посмотрели на то, как они выглядят, как выглядит `stack trace`, обсудили, как ведет себя Python при генерации исключений. Также посмотрели на то, какие типы исключений бывают - это пользовательские исключения и исключения стандартной библиотеки. Также рассмотрели иерархию исключений в стандартной библиотеке и поговорили про их обработку. Мы продолжим обсуждать то, как устроены исключения, на следующей лекции." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Реализация простого класса для чтения из файла.ipynb b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Реализация простого класса для чтения из файла.ipynb new file mode 100644 index 0000000..d901c5b --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Реализация простого класса для чтения из файла.ipynb @@ -0,0 +1,78 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "33f6f796", + "metadata": {}, + "source": [ + "# Реализация простого класса для чтения из файла #" + ] + }, + { + "cell_type": "markdown", + "id": "3190d19d", + "metadata": {}, + "source": [ + "Первое задание у нас для разогрева. Ваша задача написать Python-модуль `solution.py`, внутри которого определен класс `FileReader`.\n", + "\n", + "Инициализатор этого класса принимает аргумент - путь до файла на диске.\n", + "\n", + "У класса должен быть метод `read`, возвращающий содержимое файла в виде строки.\n", + "\n", + "Еще один момент - внутри метода `read` вы должны обрабатывать исключение `IOError`, возникающее, когда файла, с которым был инициализирован класс, на самом деле нет на жестком диске. В случае возникновения такой ошибки метод `read` должен возвращать пустую строку \"\".\n", + "\n", + "То есть класс должен работать следующим образом:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "07c2ccec", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import FileReader" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "2536881e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "reader = FileReader(\"example.txt\")\n", + "print(reader.read())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Тест по блоку (Clear).ipynb b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Тест по блоку (Clear).ipynb new file mode 100644 index 0000000..a07a4b1 --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Тест по блоку (Clear).ipynb @@ -0,0 +1,169 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cd2c7322", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "4bfab759", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "f17e8ddf", + "metadata": {}, + "source": [ + "##### 1. Отметьте все исключения стандартной библиотеки Python\n", + "\n", + "- [ ] `RequestException`\n", + "- [ ] `IndexError`\n", + "- [ ] `ValueError`\n", + "- [ ] `KeyboardInterrupt`" + ] + }, + { + "cell_type": "markdown", + "id": "111c86f4", + "metadata": {}, + "source": [ + "##### 2. Какие из методов генерации исключения разрешены в Python?\n", + "\n", + "- [ ] `raise ValueError(\"error\")`\n", + "- [ ] `raise None`\n", + "- [ ] `raise \"ValueError\"`\n", + "- [ ] `raise ValueError`" + ] + }, + { + "cell_type": "markdown", + "id": "c1818260", + "metadata": {}, + "source": [ + "##### 3. Обращение к несуществующему атрибуту экземпляра\n", + "\n", + "- [ ] сгенерирует исключение `AttributeError`\n", + "- [ ] вернет `False`\n", + "- [ ] вернет `None`\n", + "- [ ] сгенерирует исключение `KeyError`" + ] + }, + { + "cell_type": "markdown", + "id": "b3b663ae", + "metadata": {}, + "source": [ + "##### 4. Отметьте верные утверждения про `classmethod`\n", + "\n", + "- [ ] Метод не принимает дополнительных аргументов кроме указанных программистом\n", + "- [ ] Метод первым аргументом принимает класс\n", + "- [ ] Метод первым аргументом принимает ссылку на экземпляр класса\n", + "- [ ] К этому методу можно обращаться от имени класса\n", + "- [ ] К этому методу можно обращаться от экземпляра класса" + ] + }, + { + "cell_type": "markdown", + "id": "83062bcf", + "metadata": {}, + "source": [ + "##### 5. Отметьте верные утверждения про `staticmethod`\n", + "\n", + "- [ ] Метод первым аргументом принимает ссылку на экземпляр класса\n", + "- [ ] Метод первым аргументом принимает класс\n", + "- [ ] К этому методу можно обращаться от имени класса\n", + "- [ ] Метод не принимает дополнительных аргументов кроме указанных программистом\n", + "- [ ] К этому методу можно обращаться от экземпляра класса" + ] + }, + { + "cell_type": "markdown", + "id": "de052315", + "metadata": {}, + "source": [ + "##### 6. Для чего используются `@property`?\n", + "\n", + "- [ ] Чтобы делать методы приватными\n", + "- [ ] Чтобы делать атрибуты приватными\n", + "- [ ] Чтобы создать вычисляемый атрибут" + ] + }, + { + "cell_type": "markdown", + "id": "12760f65", + "metadata": {}, + "source": [ + "##### 7. Если имя метода в классе начинается с символа нижнего подчеркивания, например: `_get_name`\n", + "\n", + "- [ ] символ нижнего подчеркивания в начале метода не добавляет никакого дополнительного значения\n", + "- [ ] если вызвать метод у экземпляра класса, то сгенерируется исключение `AttributeError`\n", + "- [ ] обращаться к методу объекта не рекомендуется, так как метод не считается публичным API класса" + ] + }, + { + "cell_type": "markdown", + "id": "b60a0d44", + "metadata": {}, + "source": [ + "##### 8. Можно ли использовать экземпляры классов в качестве ключей словаря (`dict`)?\n", + "\n", + "- [ ] Да\n", + "- [ ] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "a25fe512", + "metadata": {}, + "source": [ + "##### 9. Можно ли передавать экземпляр класса как аргумент в функцию?\n", + "\n", + "- [ ] Да\n", + "- [ ] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "6d084df3", + "metadata": {}, + "source": [ + "##### 10. Предположим есть базовый класс питомец - `Pet` и класс наследник - `Cat`. Отметьте варианты, которые вернут `True`\n", + "\n", + "- [ ] `issubclass(Cat, Pet)`\n", + "- [ ] `isinstance(Cat(), Cat)`\n", + "- [ ] `issubclass(Cat, object)`\n", + "- [ ] `isinstance(Cat(), Pet)`\n", + "- [ ] `issubclass(Pet, Cat)`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Тест по блоку.ipynb b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Тест по блоку.ipynb new file mode 100644 index 0000000..2758c82 --- /dev/null +++ b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Тест по блоку.ipynb @@ -0,0 +1,160 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cd2c7322", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "f17e8ddf", + "metadata": {}, + "source": [ + "1. Отметьте все исключения стандартной библиотеки Python\n", + "\n", + "- [ ] `RequestException`\n", + "- [x] `IndexError`\n", + "- [x] `ValueError`\n", + "- [x] `KeyboardInterrupt`" + ] + }, + { + "cell_type": "markdown", + "id": "111c86f4", + "metadata": {}, + "source": [ + "2. Какие из методов генерации исключения разрешены в Python?\n", + "\n", + "- [x] `raise ValueError(\"error\")`\n", + "- [ ] `raise None`\n", + "- [ ] `raise \"ValueError\"`\n", + "- [x] `raise ValueError`" + ] + }, + { + "cell_type": "markdown", + "id": "c1818260", + "metadata": {}, + "source": [ + "3. Обращение к несуществующему атрибуту экземпляра\n", + "\n", + "- [x] сгенерирует исключение `AttributeError`\n", + "- [ ] вернет `False`\n", + "- [ ] вернет `None`\n", + "- [ ] сгенерирует исключение `KeyError`" + ] + }, + { + "cell_type": "markdown", + "id": "b3b663ae", + "metadata": {}, + "source": [ + "4. Отметьте верные утверждения про `classmethod`\n", + "\n", + "- [ ] Метод не принимает дополнительных аргументов кроме указанных программистом\n", + "- [x] Метод первым аргументом принимает класс\n", + "- [ ] Метод первым аргументом принимает ссылку на экземпляр класса\n", + "- [x] К этому методу можно обращаться от имени класса\n", + "- [x] К этому методу можно обращаться от экземпляра класса" + ] + }, + { + "cell_type": "markdown", + "id": "83062bcf", + "metadata": {}, + "source": [ + "5. Отметьте верные утверждения про `staticmethod`\n", + "\n", + "- [ ] Метод первым аргументом принимает ссылку на экземпляр класса\n", + "- [ ] Метод первым аргументом принимает класс\n", + "- [x] К этому методу можно обращаться от имени класса\n", + "- [x] Метод не принимает дополнительных аргументов кроме указанных программистом\n", + "- [x] К этому методу можно обращаться от экземпляра класса" + ] + }, + { + "cell_type": "markdown", + "id": "de052315", + "metadata": {}, + "source": [ + "6. Для чего используются `@property`?\n", + "\n", + "- [ ] Чтобы делать методы приватными\n", + "- [ ] Чтобы делать атрибуты приватными\n", + "- [x] Чтобы создать вычисляемый атрибут" + ] + }, + { + "cell_type": "markdown", + "id": "12760f65", + "metadata": {}, + "source": [ + "7. Если имя метода в классе начинается с символа нижнего подчеркивания, например: `_get_name`\n", + "\n", + "- [ ] символ нижнего подчеркивания в начале метода не добавляет никакого дополнительного значения\n", + "- [ ] если вызвать метод у экземпляра класса, то сгенерируется исключение `AttributeError`\n", + "- [x] обращаться к методу объекта не рекомендуется, так как метод не считается публичным API класса" + ] + }, + { + "cell_type": "markdown", + "id": "b60a0d44", + "metadata": {}, + "source": [ + "8. Можно ли использовать экземпляры классов в качестве ключей словаря (`dict`)?\n", + "\n", + "- [x] Да\n", + "- [ ] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "a25fe512", + "metadata": {}, + "source": [ + "9. Можно ли передавать экземпляр класса как аргумент в функцию?\n", + "\n", + "- [x] Да\n", + "- [ ] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "6d084df3", + "metadata": {}, + "source": [ + "10. Предположим есть базовый класс питомец - `Pet` и класс наследник - `Cat`. Отметьте варианты, которые вернут `True`\n", + "\n", + "- [x] `issubclass(Cat, Pet)`\n", + "- [x] `isinstance(Cat(), Cat)`\n", + "- [x] `issubclass(Cat, object)`\n", + "- [x] `isinstance(Cat(), Pet)`\n", + "- [ ] `issubclass(Pet, Cat)`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/3. Объектно-ориентированное программирование/3. Работа с ошибками/Тест по блоку.pdf b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Тест по блоку.pdf new file mode 100644 index 0000000..f5d30f2 Binary files /dev/null and b/3. Объектно-ориентированное программирование/3. Работа с ошибками/Тест по блоку.pdf differ diff --git a/3. Объектно-ориентированное программирование/Readme.ipynb b/3. Объектно-ориентированное программирование/Readme.ipynb new file mode 100644 index 0000000..fa7a311 --- /dev/null +++ b/3. Объектно-ориентированное программирование/Readme.ipynb @@ -0,0 +1,107 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "04130c52", + "metadata": {}, + "source": [ + "# Объектно-ориентированное программирование #\n", + "\n", + "В третьем блоке вас ждёт погружение в мир объектно-ориентированного программирования на Python. Вы научитесь создавать свои классы, применять наследование и обрабатывать исключения в программах." + ] + }, + { + "cell_type": "markdown", + "id": "3ead1867", + "metadata": {}, + "source": [ + "## Задачи обучения ##\n", + "\n", + "- Научиться создавать классы и работать с ними.\n", + "- Научиться обрабатывать исключения.\n", + "- Научиться работать с файлами с помощью языка Python.\n", + "- Знать механизмы наследования и уметь их применять." + ] + }, + { + "cell_type": "markdown", + "id": "0c7c6986", + "metadata": {}, + "source": [ + "## Оглавление ##" + ] + }, + { + "cell_type": "markdown", + "id": "a4f7427a", + "metadata": {}, + "source": [ + "### Классы и объекты ###\n", + "\n", + "- [Классы и экземпляры](1.%20Классы%20и%20объекты/Классы%20и%20экземпляры.ipynb)\n", + "- [Методы](1.%20Классы%20и%20объекты/Методы.ipynb)\n", + "- [Документация](1.%20Классы%20и%20объекты/Документация.ipynb)\n", + "- [Пример на классы](1.%20Классы%20и%20объекты/Пример%20на%20классы.ipynb)\n", + "- [Тест по классам и объектам](1.%20Классы%20и%20объекты/Тест%20по%20классам%20и%20объектам.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "4ba3bf64", + "metadata": {}, + "source": [ + "### Наследование ###\n", + "\n", + "- [Наследование в Python](2.%20Наследование/Наследование%20в%20Python.ipynb)\n", + "- [Композиция классов](2.%20Наследование/Композиция%20классов.ipynb)\n", + "- [Документация](2.%20Наследование/Документация.ipynb)\n", + "- [Тест по наследованию](2.%20Наследование/Тест%20по%20наследованию.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "27b35ced", + "metadata": {}, + "source": [ + "### Работа с ошибками ###\n", + "\n", + "- [Классы исключений и их обработка](3.%20Работа%20с%20ошибками/Классы%20исключений%20и%20их%20обработка.ipynb)\n", + "- [Генерация исключений](3.%20Работа%20с%20ошибками/Генерация%20исключений.ipynb)\n", + "- [Исключения в requests](3.%20Работа%20с%20ошибками/Исключения%20в%20requests.ipynb)\n", + "- [Документация](3.%20Работа%20с%20ошибками/Документация.ipynb)\n", + "- [Тест по блоку](3.%20Работа%20с%20ошибками/Тест%20по%20блоку.ipynb)\n", + "- [Реализация простого класса для чтения из файла](3.%20Работа%20с%20ошибками/Реализация%20простого%20класса%20для%20чтения%20из%20файла.ipynb)\n", + "- [Задания про классы и наследование](3.%20Работа%20с%20ошибками/Задания%20про%20классы%20и%20наследование.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "58e29197", + "metadata": {}, + "source": [ + "В этом блоке мы познакомились с устройством классов в языке Python. Теперь вы знаете как объявить свой класс, добавить к нему атрибут, метод, создать экземпляр и обратиться к атрибуту или методу. Также мы обсудили то, как устроено наследование в языке Python, рассмотрели примеры с множественным наследованием. Теперь мы знаем, как Python вызывает методы в сложной иерархии классов. Также мы рассмотрели примеры для работы с исключениями. Все эти инструменты вы можете использовать при выполнении домашних заданий и написании собственных программ на языке Python. В следующем блоке мы продолжим изучение объектно-ориентированного программирования на языке Python. [Далее...](../4.%20Углубленный%20Python/Readme.ipynb)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/1. Особые методы классов/access_log.log b/4. Углубленный Python/1. Особые методы классов/access_log.log new file mode 100644 index 0000000..3722e3d --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/access_log.log @@ -0,0 +1 @@ +New Access \ No newline at end of file diff --git a/4. Углубленный Python/1. Особые методы классов/file.py b/4. Углубленный Python/1. Особые методы классов/file.py new file mode 100644 index 0000000..47bd7b5 --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/file.py @@ -0,0 +1,64 @@ +"""Реализация класса File с магическими методами""" + +from os.path import join +from tempfile import gettempdir + + +class File: + """Класс для работы с файлами""" + + def __init__(self, file_path: "str"): + """Конструктор базового класса""" + + self.file_path: "str" = file_path + self.current_position: "int" = 0 + + def write(self, line: "str") -> None: + """Запись строки в файл""" + + with open(self.file_path, "w", encoding="utf-8") as des: + des.write(line) + + def read(self) -> "str": + """Чтение файла""" + + try: + with open(self.file_path, encoding="utf-8") as des: + content: "str" = des.read() + + except IOError: + content = "" + + return content + + def __add__(self, obj: "File") -> "File": + """Сложение двух объектов""" + + new_file = type(self)(join(gettempdir(), "temp.txt")) + new_file.write(self.read() + obj.read()) + + return new_file + + def __str__(self) -> "str": + """Строковое представление объекта""" + + return self.file_path + + def __iter__(self) -> "File": + """Метод возращающий итератор""" + + return self + + def __next__(self) -> "str": + with open(self.file_path, "r", encoding="utf-8") as des: + des.seek(self.current_position) + + line: "str" = des.readline() + + if not line: + self.current_position = 0 + raise StopIteration("EOF") + + self.current_position = des.tell() + + return line diff --git a/4. Углубленный Python/1. Особые методы классов/log.txt b/4. Углубленный Python/1. Особые методы классов/log.txt new file mode 100644 index 0000000..7a36840 --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/log.txt @@ -0,0 +1 @@ +Oh Danny boy... \ No newline at end of file diff --git a/4. Углубленный Python/1. Особые методы классов/test.log b/4. Углубленный Python/1. Особые методы классов/test.log new file mode 100644 index 0000000..b3b2ed9 --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/test.log @@ -0,0 +1 @@ +Inside `open_file` context manager \ No newline at end of file diff --git a/4. Углубленный Python/1. Особые методы классов/Документация.ipynb b/4. Углубленный Python/1. Особые методы классов/Документация.ipynb new file mode 100644 index 0000000..cf6b3d4 --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/Документация.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f31366bb", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "47575f52", + "metadata": {}, + "source": [ + "[Магические методы](https://docs.python.org/3/reference/datamodel.html \"3. Data model\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/1. Особые методы классов/Итераторы.ipynb b/4. Углубленный Python/1. Особые методы классов/Итераторы.ipynb new file mode 100644 index 0000000..5759c5e --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/Итераторы.ipynb @@ -0,0 +1,309 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "992d2d16", + "metadata": {}, + "source": [ + "# Итераторы #" + ] + }, + { + "cell_type": "markdown", + "id": "bc05e33b", + "metadata": {}, + "source": [ + "В этой лекции мы с вами поговорим про итераторы. На самом деле, с итераторами вы уже работали раньше, когда, например, использовали функцию `range` и цикл `for`. Цикл `for` позволяет вам бежать по какому-то итератору и, например, выводить все числа, как в случае с функцией `range`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "21e1622c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "0\n", + "1\n", + "0\n" + ] + } + ], + "source": [ + "for number in range(5):\n", + " print(number & 1)" + ] + }, + { + "cell_type": "markdown", + "id": "8fafa013", + "metadata": {}, + "source": [ + "Также простейшим итератором является строка или коллекция." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5d32d58a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "112\n", + "121\n", + "116\n", + "104\n", + "111\n", + "110\n" + ] + } + ], + "source": [ + "for letter in 'python':\n", + " print(ord(letter))" + ] + }, + { + "cell_type": "markdown", + "id": "89519d9e", + "metadata": {}, + "source": [ + "Итератор — это какой-то объект, по которому вы можете бежать, то есть итерироваться. Можно создать свой простейший итератор при помощи встроенной функции `iter` и, например, передать ей список." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d397be0d", + "metadata": {}, + "outputs": [], + "source": [ + "iterator = iter([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "67f95b4d", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n" + ] + } + ], + "source": [ + "print(next(iterator))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c59e91cc", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] + } + ], + "source": [ + "print(next(iterator))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e86be523", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n" + ] + } + ], + "source": [ + "print(next(iterator))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f16a382b", + "metadata": {}, + "outputs": [ + { + "ename": "StopIteration", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mStopIteration\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0miterator\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mStopIteration\u001b[0m: " + ] + } + ], + "source": [ + "print(next(iterator))" + ] + }, + { + "cell_type": "markdown", + "id": "44a59bd2", + "metadata": {}, + "source": [ + "Внутри протокол итерации работает очень просто. У нас каждый раз, когда мы хотим получить следующий элемент, вызывается функция `next`, и она возвращает следующий элемент. В данном случае это 1, 2 и 3. Когда у нас элементы исчерпаны, то есть итератор закончился, у нас выбрасывается исключение `StopIteration`, которое говорит о том, что, например, нужно выйти из цикла `for`." + ] + }, + { + "cell_type": "markdown", + "id": "c76cb6b5", + "metadata": {}, + "source": [ + "В Python'е вы, конечно, можете реализовать свой собственный итератор, написав класс с соответствующими магическими методами. Эти магические методы — это методы `__iter__` и `__next__`. Метод `__iter__` должен возвращать итератор в себя, а метод `__next__` определяет то, какие элементы возвращаются из итератора при, собственно, итерации.\n", + "\n", + "Давайте напишем свой класс `SquareIterator`, который будет каким-то аналогом функции `range`, только будет возвращать квадраты чисел. То есть `SquareIterator` принимает границы, внутри которых мы будем итерироваться, и возвращает квадраты чисел внутри этих лимитов." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8edb11e1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "4\n", + "9\n" + ] + } + ], + "source": [ + "class SquareIterator:\n", + " def __init__(self, start, end):\n", + " self.current = start\n", + " self.end = end\n", + " \n", + " def __iter__(self):\n", + " return self\n", + " \n", + " def __next__(self):\n", + " if self.current >= self.end:\n", + " raise StopIteration\n", + " \n", + " result = self.current ** 2\n", + " self.current += 1\n", + " return result\n", + "\n", + "for num in SquareIterator(1, 4):\n", + " print(num)" + ] + }, + { + "cell_type": "markdown", + "id": "3b25cc95", + "metadata": {}, + "source": [ + "В функции `__init__` мы сохраняем наши пределы, и в функции `__next__` мы будем и в функции говорить о том, что происходит при вызове следующего элемента Если у нас элементы исчерпаны, то есть у нас `current` превысил `end`, мы выбрасываем исключение `StopIteration`, которое говорит протоколу итерации о том, что итерация должна закончиться. Ну а в любом другом случае мы просто возводим число в квадрат и инкрементируем счётчик." + ] + }, + { + "cell_type": "markdown", + "id": "7a915cf7", + "metadata": {}, + "source": [ + "Таким образом мы можем использовать наш класс для того, чтобы итерироваться по нему и использовать знакомый вам цикл `for` и выводить числа, например, квадраты от 1 до 4, не включая 4." + ] + }, + { + "cell_type": "markdown", + "id": "0f31137a", + "metadata": {}, + "source": [ + "Python позволяет вам создавать собственные итераторы, и иногда это бывает полезно, когда вам нужно поддержать протокол итерации в каком-то своём классе. Что интересно, можно также определить свой собственный итератор, не определяя `__iter__` и `__next__`. Это можно сделать, написав у класса метод `__getitem__`, который определяет работу класса при обращении к его объектам с помощью квадратных скобочек, то есть как к контейнеру. Мы можем создать свой собственный контейнер `IndexIterable`, который определит `__getitem__`, и вы уже в этом случае можете по нему итерироваться." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9e0b2779", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "s\n", + "t\n", + "r\n" + ] + } + ], + "source": [ + "class IndexIterable:\n", + " def __init__(self, obj):\n", + " self.obj = obj\n", + " \n", + " def __getitem__(self, index):\n", + " return self.obj[index]\n", + "\n", + "for letter in IndexIterable(\"str\"):\n", + " print(letter)" + ] + }, + { + "cell_type": "markdown", + "id": "a7128fd1", + "metadata": {}, + "source": [ + "Это делается довольно редко. Чаще всего для того чтобы определить свой итератор, используются именно методы `__iter__` и `__next__`. На этом всё." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/1. Особые методы классов/Контекстные менеджеры.ipynb b/4. Углубленный Python/1. Особые методы классов/Контекстные менеджеры.ipynb new file mode 100644 index 0000000..e1d5ba5 --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/Контекстные менеджеры.ipynb @@ -0,0 +1,294 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e5872b4b", + "metadata": {}, + "source": [ + "# Контекстные менеджеры #" + ] + }, + { + "cell_type": "markdown", + "id": "2219c4bc", + "metadata": {}, + "source": [ + "В этой лекции мы поговорим с вами о контекстных менеджерах. С контекстными менеджерами вы уже работаете и работали, когда открывали, например, файлы. Мы с вами конкретно не останавливались на том, как это происходит внутри, но вы знаете, что если использовать контекстный менеджер `with` с открытием файла, вам не нужно заботиться о том, чтобы его потом закрыть, то есть контекстный менеджер делает это за вас. Вы открываете файл, записываете открытый файл в переменную `f`, записываете какие-то данные, и потом он сам как-то закрывается, вам не нужно писать `f.close()`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1673e912", + "metadata": {}, + "outputs": [], + "source": [ + "with open(\"access_log.log\", \"a\") as f:\n", + " f.write(\"New Access\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "daa038dd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "New Access\n" + ] + } + ], + "source": [ + "! type access_log.log" + ] + }, + { + "cell_type": "markdown", + "id": "c19a4d03", + "metadata": {}, + "source": [ + "Контекстные менеджеры позволяют вам делать именно это. Они позволяют определить поведение, которое происходит в начале и в конце блока исполнения, блока `with`. Часто бывает необходимо, как, например, в случае с файлами, отрывать и закрывать какой-то ресурс в обязательном порядке." + ] + }, + { + "cell_type": "markdown", + "id": "d671994d", + "metadata": {}, + "source": [ + "Например, вам нужно после открытия сокета его закрыть или закрыть обязательно какое-то соединение. Чтобы об этом не заботиться, об этом не помнить, можно использовать контекстный менеджер. Также, например, они используются при работе с транзакциями. Вам нужно обязательно либо закончить транзакцию, либо ее откатить, и вы можете определить контекстный менеджер, который будет вам управлять поведением открытия и закрытия блока кода. Чтобы определить свой контекстный менеджер, как вы могли догадаться, нужно написать свой класс с магическими методами. Эти магические методы `__enter__` и `__exit__`, которые как раз говорят о том, что происходит в начале и в конце контекстного менеджера. Давайте попробуем написать аналог контекстного менеджера стандартного для открытия файлов, назовем его `open_file`. Обратите внимание, название класса с маленькой буквы, потому что это контекстный менеджер, это не `CamelCase`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8b871709", + "metadata": {}, + "outputs": [], + "source": [ + "class open_file:\n", + " def __init__(self, filename, mode):\n", + " self.f = open(filename, mode)\n", + " \n", + " def __enter__(self):\n", + " return self.f\n", + " \n", + " def __exit__(self, *args):\n", + " self.f.close()\n" + ] + }, + { + "cell_type": "markdown", + "id": "43d49b39", + "metadata": {}, + "source": [ + "Итак, у нас контекстный менеджер используется точно так же, как и стандартный, мы вызываем `open_file`, в этот момент создается объект, то есть вызывается метод `__init__`, и мы записываем в переменную класса `f`, открытый файл с каким-то именем и открытый с каким-то `mode`'ом. Отлично. Потом эта переменная `f` записывается из метода `__enter__`. То есть из метода `__enter__` у нас возвращается что-то, если нам нужно это потом записать с помощью оператора `as`. Мы можем ничего не возвращать из `__enter__`, и тогда у нас нет смысла использовать `as`. Что логично в методе `__exit__` у нас определяется поведение, которое происходит при выходе из блока контекстного менеджера. В данном случае нам нужно закрыть обязательно файл. То есть когда у нас закончится этот блок, то есть где-то здесь, у нас произойдет закрытие файла, и вам не нужно об этом заботиться каждый раз, когда вы используете контекстный менеджер.\n", + "\n", + "Итак, мы открыли файл и записали в него какую-то строчку. Если попробовать прочитать этот файл, окажется, что она действительно там, файл у нас открылся и закрылся сам." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3e6d969b", + "metadata": {}, + "outputs": [], + "source": [ + "with open_file(\"test.log\", \"w\") as f:\n", + " f.write(\"Inside `open_file` context manager\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a983869d", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['Inside `open_file` context manager']\n" + ] + } + ], + "source": [ + "with open_file(\"test.log\", \"r\") as f:\n", + " print(f.readlines())" + ] + }, + { + "cell_type": "markdown", + "id": "da15917a", + "metadata": {}, + "source": [ + "Это очень удобно, и вы можете определять свое собственное поведение при выходе из блока. Еще одна важная особенность контекстных менеджеров - они позволяют вам управлять исключениями, которые произошли внутри блока. Мы можем эти исключения обрабатывать и определять какое-то поведение. Например, мы можем определить контекстный менеджер `suppress_exception`, который будет работать с `exception`'ами, которые произошли внутри." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "7ef93a74", + "metadata": {}, + "outputs": [], + "source": [ + "class suppress_exception:\n", + " def __init__(self, exc_type):\n", + " self.exc_type = exc_type\n", + " \n", + " def __enter__(self):\n", + " return\n", + " \n", + " def __exit__(self, exc_type, exc_value, traceback):\n", + " if exc_type == self.exc_type:\n", + " print(\"Nothing happend.\")\n", + " \n", + " return True" + ] + }, + { + "cell_type": "markdown", + "id": "c7eeca4c", + "metadata": {}, + "source": [ + "Обратите внимание, в данном случае мы не используем `as`, оператор `as`, поэтому нам не важно, что возвращается из `enter`'а, просто его определили и написали `return`. Могли написать просто `pass`. Итак, у нас есть контекстный менеджер `suppress_exception`, который принимает `exception`, то есть `exc_type`, класс `exception`'а. Мы этот `exc_type` записываем и потом будем проверять, произошло ли действительно это исключение или какое-то другое. Поведение таково, что если исключение произошло от того типа, который нам интересен, мы делаем вид, что ничего не произошло. То есть мы подавляем это исключение в `suppress mode`. Мы просто выводим, что ничего не произошло, и возвращаем `true`. Нужно обязательно вернуть `true` из `exit`'а при исключении, чтобы воспроизведение кода продолжилось и `exception` не был выброшен. Таким образом, мы можем поделить на `0` и `exception` засаппрессится, ничего не произойдет." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2fe27132", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Nothing happend.\n" + ] + } + ], + "source": [ + "with suppress_exception(ZeroDivisionError):\n", + " really_big_number = 1 / 0" + ] + }, + { + "cell_type": "markdown", + "id": "fc919dcd", + "metadata": {}, + "source": [ + "Часто бывает это очень полезно и удобно делать. Что интересно, такой контекстный менеджер уже есть в стандартной библиотеке в `contextlib`'е, и вы можете его использовать и можете посмотреть, как он на самом деле работает внутри и окажется, что он работает примерно так же." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "73c3ecb9", + "metadata": {}, + "outputs": [], + "source": [ + "import contextlib\n", + "\n", + "with contextlib.suppress(ValueError):\n", + " raise ValueError" + ] + }, + { + "cell_type": "markdown", + "id": "08097626", + "metadata": {}, + "source": [ + "Давайте попробуем написать свой собственный контекстный менеджер в качестве примера. Это будет контекстный менеджер, который считает время, проведенное внутри него. То есть у нас при открытии блока что-то произошло, и при закрытии мы должны вывести время, которое произошло внутри контекстного менеджера. Давайте напишем наш класс, назовем его `timer` и определим сразу методы `__enter__` и `__exit__`, потому что именно они говорят нам о том, что это контекстный менеджер. Отлично." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "bdca5011", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "\n", + "class timer():\n", + " def __init__(self):\n", + " self.start = time.time()\n", + " \n", + " def current_time(self):\n", + " return time.time() - self.start\n", + " \n", + " def __enter__(self):\n", + " return self\n", + " \n", + " def __exit__(self, *args):\n", + " print(f\"Elapsed: {self.current_time()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "6abe882b", + "metadata": {}, + "source": [ + "Как мы будем использовать наш класс? Мы будем использовать оператор `with timer`. Потом что-то должно случиться внутри контекстного менеджера, и мы должны вывести время, в которое это все дело происходило. Итак, чтобы нам считать время, которое прошло внутри контекстного менеджера, нам нужно где-то завести переменную, которая берет начало, собственно, которая и записывает время в начале выполнения операции. Происходит это, конечно, в методе `__init__`, потому что у нас создается объект класса. И давайте с помощью встроенного модуля `time` сохраним в переменную `start` текущее время. То есть в момент инициализации, то есть вот здесь вот, когда у нас вызвался таймер, у нас запишется текущее время. В `__enter__` мы просто вернем ничего, и в `__exit__` нам интересно вывести время, которое прошло с момента начала. Для этого мы просто напишем `time`, `time` и на `self.start`. То есть выведем время, которое как раз прошло. И чтобы у нас контекстный менеджер разрешился не мгновенно, давайте напишем здесь `time.sleep` и будем спать в течение одной секунды. Отлично. Да, у нас действительно вывелось время, давайте сделаем это немного посимпатичнее, как-то так, да. Давайте попробуем модифицировать немного контекстный менеджер, чтобы что-то возвращать из `return`'а, чтобы можно было, например, нам смотреть, сколько времени прошло на текущий момент, если у нас несколько, допустим, операций `sleep`. Здесь мы хотим, например, `as t` и вывести current_time и сделать еще один time.sleep. Что нам для этого нужно сделать? Например, мы можем вернуть самого себя в `__enter__`'е и определить метод `current_time`, который будет возвращать время, прошедшее с начала выполнения. Делает он точно то же самое, `time.time − self.start`. Ну и здесь можно тоже заменить тогда на `self.current_time`. Давайте отформатируем строчку, чтобы было понятно, что именно выводится у нас. Итак. Запускаем наш контекстный менеджер." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6dd41c72", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current: 1.0140066146850586\n", + "Elapsed: 2.028013229370117\n" + ] + } + ], + "source": [ + "with timer() as t:\n", + " time.sleep(1)\n", + " print(f\"Current: {t.current_time()}\")\n", + " time.sleep(1)" + ] + }, + { + "cell_type": "markdown", + "id": "7c052354", + "metadata": {}, + "source": [ + "Итак, у нас вначале выводится время текущее, то есть прошла одна секунда, один `sleep`, и итоговое время две секунды с небольшим. Собственно, как мы и ожидали. Мы написали с вами контекстный менеджер, который считает время, проведенное внутри него и плюс еще позволяет вам вывести время, которое прошло с момента начала внутри самого контекстного менеджера. Итак, контекстные менеджеры позволяют вам определить поведение при входе и выходе из блока кода `with`, что часто бывает полезно, например, при закрытии каких-то ресурсов или, например, при транзакционной какой-то деятельности." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/1. Особые методы классов/Магические методы.ipynb b/4. Углубленный Python/1. Особые методы классов/Магические методы.ipynb new file mode 100644 index 0000000..ce4903f --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/Магические методы.ipynb @@ -0,0 +1,673 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d6cf097e", + "metadata": {}, + "source": [ + "# Магические методы #" + ] + }, + { + "cell_type": "markdown", + "id": "6db9e5ec", + "metadata": {}, + "source": [ + "Привет. Добро пожаловать на четвёртый блок нашего с вами курса. В прошлом блоке вы разбирали объектно-ориентированное программирование на Python'е. А в этом блоке мы поподробнее познакомимся о том, как на самом деле всё работает внутри - как создаются объекты, как создаются классы, что происходит при выполнении определённых методов, и так далее.\n", + "\n", + "Давайте начнём с магических методов, с частью из которых вы уже знакомы. Итак, магический метод — это метод, определённый внутри класса, который начинается и заканчивается с двух подчёркиваний. Например, магическим методом является метод `__init__`, который отвечает за инициализацию созданного объекта. Давайте определим класс `User`, который будет переопределять магический метод `__init__`. Мы просто будем записывать полученые имя и email в атрибуты класса. Ну и можно определить, например, метод, который возвращает словарь." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "f8cc84e5", + "metadata": {}, + "outputs": [], + "source": [ + "class User:\n", + " def __init__(self, name, email):\n", + " self.name = name\n", + " self.email = email\n", + " \n", + " def get_email_data(self):\n", + " return {\n", + " \"name\": self.name,\n", + " \"email\": self.email\n", + " }" + ] + }, + { + "cell_type": "markdown", + "id": "bb1f0133", + "metadata": {}, + "source": [ + "Теперь при создании класса мы передадим атрибуты, которые запишутся в соответствующие значения нашего объекта. Ну и мы можем вызвать какой-то метод. С этим вы уже должны быть знакомы, потому что работали с классами уже довольно долго." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "156d080e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'name': 'Jane Doe', 'email': 'janedoe@example.com'}\n" + ] + } + ], + "source": [ + "jane = User(\"Jane Doe\", \"janedoe@example.com\")\n", + "print(jane.get_email_data())" + ] + }, + { + "cell_type": "markdown", + "id": "05b3d341", + "metadata": {}, + "source": [ + "Ещё одним магическим методом является метод `__new__`, который отвечает за то, что происходит в момент создания объекта класса. Метод `__new__` возвращает только что созданный объект класса и может как-то переопределять поведение при создании. Например, можно создать класс `Singleton`, который гарантирует то, что объект может быть создан только один от этого класса." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "81dbbd40", + "metadata": {}, + "outputs": [], + "source": [ + "class Singleton:\n", + " instance = None\n", + " \n", + " def __new__(cls):\n", + " if cls.instance is None:\n", + " cls.instance = super().__new__(cls)\n", + " \n", + " return cls.instance" + ] + }, + { + "cell_type": "markdown", + "id": "700b1e8e", + "metadata": {}, + "source": [ + "Например, мы можем попытаться создать два объекта `a` и `b`, и окажется, что это один и тот же объект, потому что мы проверяем, был ли уже создан какой-то объект, и, собственно, если он уже создан, мы не будем создавать новый объект." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "15a2b9d0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = Singleton()\n", + "b = Singleton()\n", + "\n", + "a is b" + ] + }, + { + "cell_type": "markdown", + "id": "0714f906", + "metadata": {}, + "source": [ + "Существует также метод `__del__`, который определяет поведение при удалении объекта. Однако, он работает не всегда очевидно. Он вызывается не когда мы удаляем объект оператором `del`, а когда количество ссылок на наш объект стало равно нулю и вызывается `garbage collector`. Это не всегда происходит тогда, когда мы думаем это должно произойти, поэтому переопределять его нежелательно. Будьте внимательны.\n", + "\n", + "Собственно, на этой лекции мы просмотрим какой-то набор магических методов, который чаще всего используется и переопределяется. Посмотрим, как они себя ведут и как писать классы, которые их используют.\n", + "\n", + "Одним из магических методов является метод `__str__`, который определяет поведение, например, при вызове функции `print`. Метод `__str__` должен определить какое-то человекочитаемое описание нашего класса, которое может вывести потом пользователь где-то, например, в интерфейсе. Классическим вариантом метода `__str__` может быть выведение имени и email." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "224f2c77", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Jane Doe \n" + ] + } + ], + "source": [ + "class User:\n", + " def __init__(self, name, email):\n", + " self.name = name\n", + " self.email = email\n", + " \n", + " def __str__(self):\n", + " return f\"{self.name} <{self.email}>\"\n", + " \n", + "jane = User(\"Jane Doe\", \"janedoe@example.com\")\n", + "print(jane)" + ] + }, + { + "cell_type": "markdown", + "id": "506ab28e", + "metadata": {}, + "source": [ + "Обратите внимание, мы используем тот же самый класс, что и раньше, но теперь, если мы будем принтить наш объект, у нас будет не какой-то `object` типа `user`, а понятное и читаемое название нашего объекта.\n", + "\n", + "Ещё двумя полезными методами магическими являются методы `__hash__` и `equal`,`__eq__`, которые определяют то, как сравниваются наши объекты и что происходит при вызове функции `hash`. Магический метод `__hash__` может, например, переопределить функцию хеширования, которая используется, например, когда мы получаем ключи в словаре." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "aff3ed05", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "class User:\n", + " def __init__(self, name, email):\n", + " self.name = name\n", + " self.email = email\n", + " \n", + " def __hash__(self):\n", + " return hash(self.email)\n", + " \n", + " def __eq__(self, obj):\n", + " return self.email == obj.email\n", + " \n", + "jane = User(\"Jane Doe\", \"jdoe@example.com\")\n", + "joe = User(\"Joe Doe\", \"jdoe@example.com\")\n", + "\n", + "print(jane == joe)" + ] + }, + { + "cell_type": "markdown", + "id": "bed51c07", + "metadata": {}, + "source": [ + "В данном случае нашего класса `user` мы можем сказать, что при вызове функции `__hash__` мы хотим хешировать email, то есть у нас в качестве хеша берётся всегда имейл пользователя. Ну а при сравнении мы эти email просто сравниваем, при сравнении двух объектов. Таким образом, если мы создадим двух юзеров с разными именами, но одинаковыми имейлами, при вызове функции сравнения, то есть когда мы используем `==`, Python будет говорить нам, что это один и тот же объект, потому что вызывается метод `__eq__`, который сравнивает только email. Точно так же функция `__hash__` возвращает теперь одно и то же значение, потому что используется значение email, которое в данном случае одинаковое.\n", + "\n", + "Но если мы попробуем создать словарь, где в качестве ключа будет использоваться наш объект юзера, то у нас создастся словарь только с одним ключом, а не с двумя объектами, несмотря на то, что мы итерируемся здесь по двум объектам, потому что в качестве хеша используется одно и то же значение, и в качестве `__eq__` у нас сравниваются email." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8c53d626", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2122083289\n", + "2122083289\n" + ] + } + ], + "source": [ + "print(hash(jane))\n", + "print(hash(joe))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6ebd5adf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{<__main__.User object at 0x038176E8>: 'Joe Doe'}\n" + ] + } + ], + "source": [ + "user_email_map = {user: user.name for user in [jane, joe]}\n", + "\n", + "print(user_email_map)" + ] + }, + { + "cell_type": "markdown", + "id": "5301ba65", + "metadata": {}, + "source": [ + "Очень важными магическими методами являются методы, определяющие доступ к атрибутам. Это методы `__getattr__` и `__getattribute__`. Очень важно понимать между ними отличия, потому что очень часто происходит путаница. Итак, метод `__getattr__` определяет поведение, когда наш атрибут, который мы пытаемся получить, не найден. Таким образом, если мы обратимся к атрибуту какого-то объекта и у нас он будет не найден, у нас всегда вызовется метод `__getattr__` и мы можем определить какое-то поведение дефолтное при той ситуации, когда атрибута нет.\n", + "\n", + "Метод `__getattribute__` вызывается в любом случае. Когда мы обращаемся к какому-то атрибуту, у нас всегда вызывается метод `__getattribute__`, и мы можем, например, логировать какие-то обращения к атрибутам или переопределять поведение при поиске соответствующих атрибутов объекта. Например, мы можем возвращать всегда какую-то строчку и ничего не делать, как в данном случае." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "fdfe0e1e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "nope\n", + "nope\n", + "nope\n" + ] + } + ], + "source": [ + "class Researcher:\n", + " def __getattr__(self, name):\n", + " return \"Nothing found :(\"\n", + " \n", + " def __getattribute__(self, name):\n", + " return \"nope\"\n", + " \n", + "obj = Researcher()\n", + "\n", + "print(obj.attr)\n", + "print(obj.method)\n", + "print(obj.DFG2H3J00KLL)" + ] + }, + { + "cell_type": "markdown", + "id": "d70a8034", + "metadata": {}, + "source": [ + "Мы определили класс и переопределили метод `__getattribute__`, который всегда возвращает строку и ничего дальше не делает. Таким образом, что бы мы не делали, как бы мы не пытались обращаться к атрибутам, даже которых ещё нет, как в данном случае, у нас всегда выведется эта строка. `__getattr__` работает немного по-другому. `__getattr__`, как я уже говорил, вызывается в том случае, если атрибут не найден." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2b992fcb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Looking for attr\n", + "Nothing found :(\n", + "Looking for method\n", + "Nothing found :(\n", + "Looking for DFG2H3J00KLL\n", + "Nothing found :(\n" + ] + } + ], + "source": [ + "class Researcher:\n", + " def __getattr__(self, name):\n", + " return \"Nothing found :(\"\n", + " \n", + " def __getattribute__(self, name):\n", + " print(f\"Looking for {name}\")\n", + " return object.__getattribute__(self, name)\n", + " \n", + "obj = Researcher()\n", + "\n", + "print(obj.attr)\n", + "print(obj.method)\n", + "print(obj.DFG2H3J00KLL)" + ] + }, + { + "cell_type": "markdown", + "id": "981d22e6", + "metadata": {}, + "source": [ + "В данном случае внутри `__getattribute__`, который вызывается всегда, мы просто логируем, что мы пытаемся найти соответствующий атрибут и продолжаем выполнение, используя класс `object`. Таким образом, если у нас пытается найтись атрибут, которого не существует, у нас вызовется `__getattr__`, что здесь и происходит. У нас ничего не найдено. Отлично." + ] + }, + { + "cell_type": "markdown", + "id": "aa031adb", + "metadata": {}, + "source": [ + "`__setattr__`, как вы могли догадаться, определяет поведение при присваивании значения к атрибуту. Например, вместо того, чтобы присвоить значение, мы можем опять же вернуть какую-то строчку и ничего не делать. В данном случае, если мы попытаемся присвоить значение атрибуту, у нас ничего не произойдёт и атрибут не создастся." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "7b83ad70", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Not gonna set math!\n" + ] + } + ], + "source": [ + "class Ignorant:\n", + " def __setattr__(self, name, value):\n", + " print(f\"Not gonna set {name}!\")\n", + "\n", + "obj = Ignorant()\n", + "obj.math = True" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "fb3f8c31", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'Ignorant' object has no attribute 'math'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmath\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m: 'Ignorant' object has no attribute 'math'" + ] + } + ], + "source": [ + "print(obj.math)" + ] + }, + { + "cell_type": "markdown", + "id": "852a889f", + "metadata": {}, + "source": [ + "Ну а `__delattr__` управляет поведением, когда мы пытаемся удалить какой-то атрибут объекта. Мы можем не удалять, а, например, переопределить как-то поведение или добавить какую-то функциональность. Например, если мы хотим каскадно удалить какие-то объекты, связанные с нашим классом." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "684b1176", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Goodbye attr, you were 10!\n" + ] + } + ], + "source": [ + "class Polite:\n", + " def __delattr__(self, name):\n", + " value = getattr(self, name)\n", + " print(f\"Goodbye {name}, you were {value}!\")\n", + " object.__delattr__(self, name)\n", + "\n", + "obj = Polite()\n", + "obj.attr = 10\n", + "del obj.attr" + ] + }, + { + "cell_type": "markdown", + "id": "dffcc1d4", + "metadata": {}, + "source": [ + "В данном случае мы просто продолжаем удаление с помощью класса `object`, ну и как-то логируем то, что у нас происходит удаление.\n", + "\n", + "Ещё одним методом является метод `__call__`, который в соответствии со своим названием определяет поведение, когда наш класс вызывается. Например, с помощью метода `__call__` мы можем определить logger, который будем потом использовать в качестве декоратора. Да, декоратором может быть не только функция, но и класс." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "f16c0418", + "metadata": {}, + "outputs": [], + "source": [ + "class Logger:\n", + " def __init__(self, filename):\n", + " self.filename = filename\n", + " def __call__(self, func):\n", + " with open(self.filename, \"w\") as f:\n", + " f.write(\"Oh Danny boy...\")\n", + " \n", + " return func\n", + "\n", + "logger = Logger(\"log.txt\")\n", + "\n", + "@logger\n", + "def completely_useless_function():\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "2df4685b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Oh Danny boy...\n" + ] + } + ], + "source": [ + "completely_useless_function()\n", + "\n", + "with open('log.txt') as f:\n", + " print(f.read())" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "0decbc80", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Oh Danny boy...\n" + ] + } + ], + "source": [ + "! type log.txt" + ] + }, + { + "cell_type": "markdown", + "id": "c51d3828", + "metadata": {}, + "source": [ + "В данном случае при инициализации класса мы запоминаем `filename`, который ему передан, и каждый раз, когда мы будем вызывать наш класс, мы будем возвращать какую-то новую функцию в соответствии с протоколом декораторов и записывать значения, записывать какую-то строчку о том, что у нас наша функция вызвана. В данном случае, мы просто определяем пустую функцию, декоратор которой записывает все её вызовы. Ну и когда мы вызовем нашу функцию, у нас в нашем файле появится строка." + ] + }, + { + "cell_type": "markdown", + "id": "7c12bb61", + "metadata": {}, + "source": [ + "Классическим примером на перегрузку операторов в других языках является перегрузка оператора сложения. В данном случае за операцию сложения в Python'е отвечает оператор `__add__`. Существуют также другие операторы вроде `__sub__`, который определяет поведение при вычитании, что логично. И мы можем определить наш класс, который будет перегружать операцию сложения." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "6e7dab94", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "\n", + "class NoisyInt:\n", + " def __init__(self, value):\n", + " self.value = value\n", + " def __add__(self, obj):\n", + " noise = random.uniform(-1, 1)\n", + " return self.value + obj.value + noise\n", + "\n", + "a = NoisyInt(10)\n", + "b = NoisyInt(20)" + ] + }, + { + "cell_type": "markdown", + "id": "7b189df8", + "metadata": {}, + "source": [ + "В данном случае мы можем написать какой-то `NoisyInt`, который будет работать почти как `integer`, но добавлять какой-то шум, например, при сложении. Мы определим два наших Int'а со значением 10 и 20, и в операции сложение, то есть в методе `__add__` мы будем добавлять какое-то случайное значение от минус единицы до единицы. Таким образом, когда мы попытаемся сложить два наших числа, у нас выведется число в окрестности искомого, то есть у нас будет 29.5, например, вместо 30." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "5eb86601", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "29.838970125536626\n", + "29.283700512158727\n", + "30.074755836157177\n" + ] + } + ], + "source": [ + "for _ in range(3):\n", + " print(a + b)" + ] + }, + { + "cell_type": "markdown", + "id": "84513ba8", + "metadata": {}, + "source": [ + "Это просто пример, вы можете переопределять операцию сложения так, как вам удобно, как вам подходит для вашей задачи. \n", + "\n", + "Предлагаю вам попробовать попрактиковаться и самостоятельно написать контейнер с помощью методов `__getitem__` и `__setitem__`." + ] + }, + { + "cell_type": "markdown", + "id": "9bed86fa", + "metadata": {}, + "source": [ + "Отлично, надеюсь, у вас получилось. Ну а в качестве примера я реализовал свой собственный класс `PascalList`, который имитирует поведение списков в Паскале. Как вы знаете, в Python'e списки нумеруются с нуля, ну а в Паскале — с единицы. Мы можем переопределить методы `__getitem__` и `__setitem__` так, чтобы они работали как в Паскале. Методы `__getitem__` и `__setitem__` определяют поведение, когда мы работаем с нашим классом с помощью квадратных скобочек, обращаясь по какому-то индексу, то есть как в случае с list'ами, со списками, или в случае со словарями." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "805d5032", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n" + ] + } + ], + "source": [ + "class PascalList:\n", + " def __init__(self, original_list=None):\n", + " self.container = original_list or []\n", + " \n", + " def __getitem__(self, index):\n", + " return self.container[index - 1]\n", + " \n", + " def __setitem__(self, index, value):\n", + " self.container[index - 1] = value\n", + " \n", + " def __str__(self):\n", + " return self.container.__str__()\n", + " \n", + "numbers = PascalList([1, 2, 3, 4, 5])\n", + "print(numbers[1])" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "ff758ccd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 25]\n" + ] + } + ], + "source": [ + "numbers[5] = 25\n", + "\n", + "print(numbers)" + ] + }, + { + "cell_type": "markdown", + "id": "1475fc66", + "metadata": {}, + "source": [ + "И в данном примере мы определяем класс `PascalList`, который принимает какой-то список, запоминает его и каждый раз, когда мы пытаемся достучаться до какого-то индекса, он обращается к этому индексу минус единица. Таким образом, если мы попытаемся взять первый элемент, у нас, на самом деле, возьмётся нулевой элемент, как в Python'e. Ну и, например, мы можем создать `PascalList` от одного до пяти, и, обратившись по первому индексу, мы получим элемент один, как и сделали бы в Паскале. Ну и, например, к пятому мы можем переопределить значение в конце.\n", + "\n", + "Собственно, на этом всё, мы с вами обсудили какой-то набор магических методов. Более полно можно посмотреть про них в документации. Их огромное количество, и они управляют поведением класса в Python'e и тем, как работают объекты, созданные от этих классов." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/1. Особые методы классов/Тест по методам (Clear).ipynb b/4. Углубленный Python/1. Особые методы классов/Тест по методам (Clear).ipynb new file mode 100644 index 0000000..ec00f27 --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/Тест по методам (Clear).ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ee353f1f", + "metadata": {}, + "source": [ + "# Тест по методам #" + ] + }, + { + "cell_type": "markdown", + "id": "036697e8", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "247cf0a2", + "metadata": {}, + "source": [ + "##### 1. Какой метод класса возвращает новый созданный объект?\n", + "\n", + "- [ ] `__get__`\n", + "- [ ] `__new__`\n", + "- [ ] `__init__`\n", + "- [ ] `__create__`" + ] + }, + { + "cell_type": "markdown", + "id": "0771b868", + "metadata": {}, + "source": [ + "##### 2. Какой метод отвечает за происходящее в конце блока контекстного менеджера?\n", + "\n", + "- [ ] `__exit__`\n", + "- [ ] `__del__`\n", + "- [ ] `__end__`\n", + "- [ ] `__delete__`" + ] + }, + { + "cell_type": "markdown", + "id": "6a8d5488", + "metadata": {}, + "source": [ + "##### 3. Какой метод отвечает за отображении объекта при вызове функции print?\n", + "\n", + "- [ ] `__print__`\n", + "- [ ] `__read__`\n", + "- [ ] `__unicode__`\n", + "- [ ] `__str__`" + ] + }, + { + "cell_type": "markdown", + "id": "c9af7188", + "metadata": {}, + "source": [ + "##### 4. Какой метод отвечает за обращение к объекту по индексу?\n", + "\n", + "- [ ] `__getattr__`\n", + "- [ ] `__get__`\n", + "- [ ] `__getitem__`\n", + "- [ ] `__getattribute__`\n", + "- [ ] `__getindex__`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/1. Особые методы классов/Тест по методам.ipynb b/4. Углубленный Python/1. Особые методы классов/Тест по методам.ipynb new file mode 100644 index 0000000..20c2427 --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/Тест по методам.ipynb @@ -0,0 +1,86 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ee353f1f", + "metadata": {}, + "source": [ + "# Тест по методам #" + ] + }, + { + "cell_type": "markdown", + "id": "247cf0a2", + "metadata": {}, + "source": [ + "1. Какой метод класса возвращает новый созданный объект?\n", + "\n", + "- [ ] `__get__`\n", + "- [x] `__new__`\n", + "- [ ] `__init__`\n", + "- [ ] `__create__`" + ] + }, + { + "cell_type": "markdown", + "id": "0771b868", + "metadata": {}, + "source": [ + "2. Какой метод отвечает за происходящее в конце блока контекстного менеджера?\n", + "\n", + "- [x] `__exit__`\n", + "- [ ] `__del__`\n", + "- [ ] `__end__`\n", + "- [ ] `__delete__`" + ] + }, + { + "cell_type": "markdown", + "id": "6a8d5488", + "metadata": {}, + "source": [ + "3. Какой метод отвечает за отображении объекта при вызове функции print?\n", + "\n", + "- [ ] `__print__`\n", + "- [ ] `__read__`\n", + "- [ ] `__unicode__`\n", + "- [x] `__str__`" + ] + }, + { + "cell_type": "markdown", + "id": "3d9e03ee", + "metadata": {}, + "source": [ + "4. Какой метод отвечает за обращение к объекту по индексу?\n", + "\n", + "- [ ] `__getattr__`\n", + "- [ ] `__get__`\n", + "- [x] `__getitem__`\n", + "- [ ] `__getattribute__`\n", + "- [ ] `__getindex__`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/1. Особые методы классов/Файл с магическими методами.ipynb b/4. Углубленный Python/1. Особые методы классов/Файл с магическими методами.ipynb new file mode 100644 index 0000000..090bfb6 --- /dev/null +++ b/4. Углубленный Python/1. Особые методы классов/Файл с магическими методами.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9e725e75", + "metadata": {}, + "source": [ + "# Файл с магическими методами #" + ] + }, + { + "cell_type": "markdown", + "id": "4899791d", + "metadata": {}, + "source": [ + "В этом задании вам нужно создать интерфейс для работы с файлами. Класс `File` должен поддерживать несколько необычных операций.\n", + "\n", + "Класс инициализируется полным путем." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "f6f38438", + "metadata": {}, + "outputs": [], + "source": [ + "from file import File" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "285d10f8", + "metadata": {}, + "outputs": [], + "source": [ + "obj = File(\"/tmp/file.txt\")" + ] + }, + { + "cell_type": "markdown", + "id": "d5b3cceb", + "metadata": {}, + "source": [ + "Класс должен поддерживать метод `write`." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "457f1257", + "metadata": {}, + "outputs": [], + "source": [ + "obj.write(\"line\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "0e5ca18a", + "metadata": {}, + "source": [ + "Объекты типа `File` должны поддерживать сложение." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "aee4e554", + "metadata": {}, + "outputs": [], + "source": [ + "first = File(\"/tmp/first\")\n", + "second = File(\"/tmp/second\")\n", + "\n", + "new_obj = first + second" + ] + }, + { + "cell_type": "markdown", + "id": "370a538c", + "metadata": {}, + "source": [ + "В этом случае создается новый файл и файловый объект, в котором содержимое второго файла добавляется к содержимому первого файла. Новый файл должен создаваться в директории, полученной с помощью [tempfile.gettempdir](https://docs.python.org/3/library/tempfile.html \"11.6. tempfile — Generate temporary files and directories\"). Для получения нового пути можно использовать [os.path.join](https://docs.python.org/3/library/os.path.html#os.path.join \"1.2. os.path — Common pathname manipulations\").\n", + "\n", + "Объекты типа `File` должны поддерживать протокол итерации, причем итерация проходит по строкам файла." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "8041194e", + "metadata": {}, + "outputs": [], + "source": [ + "for line in File(\"/tmp/file.txt\"):\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "id": "c177c4f2", + "metadata": {}, + "source": [ + "И наконец, при выводе файла с помощью функции `print` должен печататься его полный путь, переданный при инициализации." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "0bc40684", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/tmp/file.txt\n" + ] + } + ], + "source": [ + "obj = File(\"/tmp/file.txt\")\n", + "\n", + "print(obj)" + ] + }, + { + "cell_type": "markdown", + "id": "b54fb2db", + "metadata": {}, + "source": [ + "Опишите свой класс в скрипте." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/2. Механизм работы классов/descriptor.py b/4. Углубленный Python/2. Механизм работы классов/descriptor.py new file mode 100644 index 0000000..1ba5dba --- /dev/null +++ b/4. Углубленный Python/2. Механизм работы классов/descriptor.py @@ -0,0 +1,41 @@ +"""Реализация дескриптора с комиссией""" + + +class Value: + """Значение""" + + def __init__(self): + """Конструктор класса""" + + self.amount: "float" = 0.0 + + def __get__(self, obj, obj_type) -> "float": + """Возвращение значения""" + + return self.amount + + def __set__(self, obj, value): + """Присваивание значения""" + + self.amount = value - value * obj.commission + + +class Account: + """Счет""" + + amount: "Value" = Value() + + def __init__(self, commission): + """Конструктор класса""" + + self.commission: "float" = commission + + def __str__(self) -> "str": + """Строковое представление класса""" + + return f"{self.commission}" + + def __repr__(self) -> "str": + """Строковое представление класса""" + + return f"Account({self.commission})" diff --git a/4. Углубленный Python/2. Механизм работы классов/log.txt b/4. Углубленный Python/2. Механизм работы классов/log.txt new file mode 100644 index 0000000..8ed0a15 --- /dev/null +++ b/4. Углубленный Python/2. Механизм работы классов/log.txt @@ -0,0 +1 @@ +200150200250 \ No newline at end of file diff --git a/4. Углубленный Python/2. Механизм работы классов/Дескриптор с комиссией.ipynb b/4. Углубленный Python/2. Механизм работы классов/Дескриптор с комиссией.ipynb new file mode 100644 index 0000000..ca6e6fe --- /dev/null +++ b/4. Углубленный Python/2. Механизм работы классов/Дескриптор с комиссией.ipynb @@ -0,0 +1,90 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "88b3ed28", + "metadata": {}, + "source": [ + "# Дескриптор с комиссией #" + ] + }, + { + "cell_type": "markdown", + "id": "84b8adbb", + "metadata": {}, + "source": [ + "Часто при зачислении каких-то средств на счет с нас берут комиссию. Давайте реализуем похожий механизм с помощью дескрипторов. Напишите дескриптор `Value`, который будет использоваться в нашем классе `Account`.\n", + "\n", + "```python\n", + "class Account:\n", + " amount = Value()\n", + " \n", + " def __init__(self, commission):\n", + " self.commission = commission\n", + "```\n", + "\n", + "У аккаунта будет атрибут `commission`. Именно эту комиссию и нужно вычитать при присваивании значений в `amount`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "52c62ed9", + "metadata": {}, + "outputs": [], + "source": [ + "from descriptor import Account" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b297b20c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "90.0\n" + ] + } + ], + "source": [ + "new_account = Account(0.1)\n", + "new_account.amount = 100\n", + "\n", + "print(new_account.amount)" + ] + }, + { + "cell_type": "markdown", + "id": "f0eac97f", + "metadata": {}, + "source": [ + "Опишите дескриптор в файле." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/2. Механизм работы классов/Дескрипторы.ipynb b/4. Углубленный Python/2. Механизм работы классов/Дескрипторы.ipynb new file mode 100644 index 0000000..c9dae79 --- /dev/null +++ b/4. Углубленный Python/2. Механизм работы классов/Дескрипторы.ipynb @@ -0,0 +1,800 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f647bc43", + "metadata": {}, + "source": [ + "# Магические методы #" + ] + }, + { + "cell_type": "markdown", + "id": "fc2cfee7", + "metadata": {}, + "source": [ + "Настало время поговорить о дескрипторах. С помощью дескрипторов в Python реализована практически вся магия при работе с объектами, классами и методами.\n", + "\n", + "Чтобы определить свой собственный дескриптор, нужно определить класс и задать методы `__get__`, `__set__` или `__delete__`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "69fc5558", + "metadata": {}, + "outputs": [], + "source": [ + "class Descriptor:\n", + " def __get__(self, obj, obj_type):\n", + " print(\"get\")\n", + " \n", + " def __set__(self, obj, value):\n", + " print(\"set\")\n", + " \n", + " def __delete__(self, obj):\n", + " print(\"delete\")" + ] + }, + { + "cell_type": "markdown", + "id": "6f32656e", + "metadata": {}, + "source": [ + "После этого мы можем создать какой-то новый класс и в атрибут этого класса записать объект типа дескриптор." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "22ef5e35", + "metadata": {}, + "outputs": [], + "source": [ + "class Class:\n", + " attr = Descriptor()" + ] + }, + { + "cell_type": "markdown", + "id": "56d62171", + "metadata": {}, + "source": [ + "Таким образом, наш атрибут будет являться дескриптором. Что это значит? У него будет переопределено поведение при доступе к атрибуту, при присваивании значений или при удалении.\n", + "\n", + "Метод `__get__`, как вы могли догадаться, определяет поведение при доступе к атрибуту. Метод `__set__` будет переопределять какое-то поведение, если мы попытаемся в наш атрибут что-то присвоить, а метод `__delete__` будет говорить о том, что будет происходить, если мы удалим наш атрибут.\n", + "\n", + "Мы создадим объект класса `Class` и посмотрим, что будет происходить при обращении к атрибуту." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6350d18c", + "metadata": {}, + "outputs": [], + "source": [ + "instance = Class()" + ] + }, + { + "cell_type": "markdown", + "id": "e678e3f9", + "metadata": {}, + "source": [ + "Если мы просто попытаемся вывести наш атрибут, у нас вызовется метод `__get__`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1c4f3b2c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "get\n" + ] + } + ], + "source": [ + "instance.attr" + ] + }, + { + "cell_type": "markdown", + "id": "e3ff5546", + "metadata": {}, + "source": [ + "Если мы запишем в него какое-то значение, у нас вызывается метод `__set__`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c8ff0399", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "set\n" + ] + } + ], + "source": [ + "instance.attr = 10" + ] + }, + { + "cell_type": "markdown", + "id": "ae92ca69", + "metadata": {}, + "source": [ + "А если мы его удаляем, вызывается метод `__delete__`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "38c4d579", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delete\n" + ] + } + ], + "source": [ + "del instance.attr" + ] + }, + { + "cell_type": "markdown", + "id": "be2f46c0", + "metadata": {}, + "source": [ + "Таким образом, Python позволяет вам переопределять поведение при доступе к атрибуту. Это очень мощная концепция, мощный механизм, который позволяет вам незаметно от пользователя определять различные поведения в ваших классах.\n", + "\n", + "Например, мы можем определить дескриптор `Value`, который будет переопределять поведение при присваивании значения в него." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bfcc07af", + "metadata": {}, + "outputs": [], + "source": [ + "class Value:\n", + " def __init__(self):\n", + " self.value = None\n", + " \n", + " @staticmethod\n", + " def _prepare_value(value):\n", + " return value * 10\n", + " \n", + " def __get__(self, obj, obj_type):\n", + " return self.value\n", + " \n", + " def __set__(self, obj, value):\n", + " self.value = self._prepare_value(value)" + ] + }, + { + "cell_type": "markdown", + "id": "0f54defa", + "metadata": {}, + "source": [ + "Мы определим наш класс с атрибутом, который будет являться дескриптором, и при присваивании значений в дескриптор у нас будет происходить модифицированное поведение." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c6e4992a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100\n" + ] + } + ], + "source": [ + "class Class:\n", + " attr = Value()\n", + " \n", + "instance = Class()\n", + "instance.attr = 10\n", + "\n", + "print(instance.attr)" + ] + }, + { + "cell_type": "markdown", + "id": "cc9892f2", + "metadata": {}, + "source": [ + "То есть наш метод `__set__` говорит о том, что, когда мы присваиваем значение в наш дескриптор, мы не просто сохраняем это значение, но мы как-то его препроцессим. В данном случае просто умножаем на десять. Таким образом, когда мы присваиваем десятку в наш атрибут, который является дескриптором, у нас, на самом деле, сохраняется сотня. В данном случае мы переопределили только два метода `__get__` и `__set__` этого уже достаточно для того, чтобы наш класс являлся дескриптором. Вы можете переопределить любой из трех методов, и класс уже будет являться дескриптором.\n", + "\n", + "Если у вас переопределен только метод `__get__`, то это `non-data` дескриптор, если `__set__` или `__delete__` — то это `data` дескриптор. Это говорит о том, в каком порядке они будут искаться, вызываться при поиске атрибутов." + ] + }, + { + "cell_type": "markdown", + "id": "fdbaee85", + "metadata": {}, + "source": [ + "### Напишем дескриптор, который пишет в файл все присваиваемые ему значения ###" + ] + }, + { + "cell_type": "markdown", + "id": "2dd70eb1", + "metadata": {}, + "source": [ + "Давайте, чтобы подробнее разобраться с тем, как работают дескрипторы, напишем, как всегда, свой дескриптор. И в данном случае это будет дескриптор, который будет записывать все значения, которые ему присваиваются, в файл.\n", + "\n", + "Мы можем представить, что это какая-то важная информация, которую всегда нужно сохранять. Можете это сохранять в данном случае в файл или, например, сохранять куда-нибудь на сервер, делать реплику.\n", + "\n", + "Давайте создадим наш класс `ImportantValue` и переопределим его методы `__get__`. Метод `__get__` принимает `obj` и `obj_type`, то есть объект, с которым вызвал дескриптор его тип, и метод `__set__`, который принимает объект, и значение, которое нужно присвоить. " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "957da1db", + "metadata": {}, + "outputs": [], + "source": [ + "class ImportantValue:\n", + " def __get__(self, obj, obj_type):\n", + " pass\n", + " \n", + " def __set__(self, obj, value):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "240e5f9a", + "metadata": {}, + "source": [ + "Таким образом, если мы создадим класс с какой-то важной информацией, например, это может быть класс `Account`, и важная информация — это, например, `amount` это какое-то, например, денежное значение, которое нам нужно всегда сохранять. И мы можем сделать это дескриптором." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "35057bd3", + "metadata": {}, + "outputs": [], + "source": [ + "class Account:\n", + " amount = ImportantValue()" + ] + }, + { + "cell_type": "markdown", + "id": "6902c993", + "metadata": {}, + "source": [ + "В данном случае наш `amount` является дескриптором с переопределенным поведением. Однако пока ничего не происходит в этом переопределенном поведении. Давайте это исправим. Определим метод `__init__`, и наш дескриптор должен принимать какое-то значение, которые мы должны сохранить. В данном случае он принимает `amount` и, допустим, просто его сохраняет." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "b842c9cc", + "metadata": {}, + "outputs": [], + "source": [ + "class ImportantValue:\n", + " def __init__(self, amount):\n", + " self.amount = amount\n", + " \n", + " def __get__(self, obj, obj_type):\n", + " pass\n", + " \n", + " def __set__(self, obj, value):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "589335ed", + "metadata": {}, + "source": [ + "Пусть у нас будет 100 каких-то единиц на счету." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "e7a70ad0", + "metadata": {}, + "outputs": [], + "source": [ + "class Account:\n", + " amount = ImportantValue(100)" + ] + }, + { + "cell_type": "markdown", + "id": "3128afb5", + "metadata": {}, + "source": [ + "Если мы будем менять значение нашего атрибута, мы хотим все это логировать. Будем всегда логировать в один и тот же файл, просто записывать. Не забудем сохранить все-таки само значение.\n", + "\n", + "Когда мы пытаемся к нему обратиться, нужно вернуть это значение из `amount`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f1c0da60", + "metadata": {}, + "outputs": [], + "source": [ + "class ImportantValue:\n", + " def __init__(self, amount):\n", + " self.amount = amount\n", + " \n", + " def __get__(self, obj, obj_type):\n", + " return self.amount\n", + " \n", + " def __set__(self, obj, value):\n", + " with open(\"log.txt\", \"w\") as f:\n", + " f.write(str(value))\n", + " \n", + " self.amount = value" + ] + }, + { + "cell_type": "markdown", + "id": "505b86ae", + "metadata": {}, + "source": [ + "Мы создали наш дескриптор. Давайте посмотрим, что будет происходить, когда мы попытаемся присвоить значение в `amount`. Для этого нам нужно создать объект класса `Account`. Пусть это будет `bobs_account`. И у Боба на счету какое-то количество единиц." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "da7c4d5a", + "metadata": {}, + "outputs": [], + "source": [ + "class Account:\n", + " amount = ImportantValue(100)\n", + " \n", + "bobs_account = Account()" + ] + }, + { + "cell_type": "markdown", + "id": "5947625a", + "metadata": {}, + "source": [ + "Таким образом, если мы попытаемся это количество единиц, количество каких-то денег изменить, то есть мы сделаем `amount` не 100, например, а 150, эти изменения должны залогироваться в файл. Давайте проверим, есть ли они там. Мы просто читаем наш файл и смотрим, что туда записалось." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "78fb9277", + "metadata": {}, + "outputs": [], + "source": [ + "bobs_account.amount = 150" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "0981db7e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "150" + ] + } + ], + "source": [ + "! cat log.txt" + ] + }, + { + "cell_type": "markdown", + "id": "698ed728", + "metadata": {}, + "source": [ + "Отлично! В нашем файле 150, потому что, когда мы присваивали значения в наш дескриптор, мы не только эти значения запоминали, но и записывали в файл. Таким образом, если мы запишем, например, 200, у нас в файле будет 200." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "82f2fd22", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "200\n" + ] + } + ], + "source": [ + "bobs_account.amount = 200\n", + "\n", + "with open(\"log.txt\", \"r\") as f:\n", + " print(f.read())" + ] + }, + { + "cell_type": "markdown", + "id": "18fbc691", + "metadata": {}, + "source": [ + "Мы можем не перезаписывать этот файл, а, например, дополнять его. Таким образом, у нас каждый раз, когда мы будем присваивать, файл будет изменяться. Мы можем логировать все записи в этот атрибут." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "074c0f7b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "200150200250\n" + ] + } + ], + "source": [ + "class ImportantValue:\n", + " def __init__(self, amount):\n", + " self.amount = amount\n", + " \n", + " def __get__(self, obj, obj_type):\n", + " return self.amount\n", + " \n", + " def __set__(self, obj, value):\n", + " with open(\"log.txt\", \"a\") as f:\n", + " f.write(str(value))\n", + " \n", + " self.amount = value\n", + " \n", + "class Account:\n", + " amount = ImportantValue(100)\n", + " \n", + "bobs_account = Account()\n", + "bobs_account.amount = 150\n", + "bobs_account.amount = 200\n", + "bobs_account.amount = 250\n", + "\n", + "with open(\"log.txt\", \"r\") as f:\n", + " print(f.read())" + ] + }, + { + "cell_type": "markdown", + "id": "fda9028f", + "metadata": {}, + "source": [ + "## Функции и методы ##" + ] + }, + { + "cell_type": "markdown", + "id": "b920bcd4", + "metadata": {}, + "source": [ + "Что ж, давайте продолжим. И на самом деле, несмотря на то, что вы пользовались функциями и методами уже довольно давно, на самом деле функции и методы реализованы с помощью дескрипторов. Чтобы понять, что это действительно так, можно попробовать обратиться к одному и тому же методу с помощью объекта и с помощью класса. И окажется, что, когда мы обращаемся к методу через точку от объекта, у нас возвращается `bound method`. То есть это метод, привязанный уже к какому-то объекту, в данном случае `object`. А если мы обращаемся к методу от `Class`, то у нас это `unbound method`. Это просто функция." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "e68a9a79", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ">\n", + "\n" + ] + } + ], + "source": [ + "class Class:\n", + " def method(self):\n", + " pass\n", + "\n", + "obj = Class()\n", + "\n", + "print(obj.method)\n", + "print(Class.method)" + ] + }, + { + "cell_type": "markdown", + "id": "c3862d17", + "metadata": {}, + "source": [ + "Как вы видите, один и тот же метод возвращает разные объекты в зависимости от того, как к нему обращаются. Это и есть поведение дескриптора.\n", + "\n", + "Вам уже знаком декоратор `property`, который позволяет вам использовать функцию как атрибут класса. В данном случае мы можем определить `property full_name`, который на самом деле хоть и является функцией, которая возвращает строчку, используется потом так же, как и обычный атрибут, то есть без вызова скобочек. В данном случае у нас класс `User`, у нас `first_name` и `last_name`, и `full_name` возвращает, очевидно, полное имя. При вызове `full_name` от объекта у нас вызывается функция `full_name`. Однако если мы пытаемся обратиться к `full_name` от класса, у нас получится объект типа `property`. На самом деле, `property` реализовано с помощью дескрипторов, потому что разное поведение в зависимости от того, как у нас вызывается этот объект." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "28f4c5db", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Amy Jones\n", + "\n" + ] + } + ], + "source": [ + "class User:\n", + " def __init__(self, first_name, last_name):\n", + " self.first_name = first_name\n", + " self.last_name = last_name\n", + " \n", + " @property\n", + " def full_name(self):\n", + " return f\"{self.first_name} {self.last_name}\"\n", + "\n", + "amy = User(\"Amy\", \"Jones\")\n", + "\n", + "print(amy.full_name)\n", + "print(User.full_name)" + ] + }, + { + "cell_type": "markdown", + "id": "72f7b367", + "metadata": {}, + "source": [ + "И мы можем написать свой собственный класс `property`, который будет эмулировать поведение стандартного `property`. Для этого нам нужно сохранить функцию, которую `property` получает, потому что `property` — это декоратор, он получает функцию. И когда мы обращаемся к нашему объекту, если он вызван от класса, мы просто возвращаем самого себя, а если у нас вызван наш атрибут с объектом, то мы возвращаем соответствующий `getter`, вызываем функцию." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "3083b097", + "metadata": {}, + "outputs": [], + "source": [ + "class Property:\n", + " def __init__(self, getter):\n", + " self.getter = getter\n", + " \n", + " def __get__(self, obj, obj_type=None):\n", + " if obj is None:\n", + " return self\n", + " \n", + " return self.getter(obj)" + ] + }, + { + "cell_type": "markdown", + "id": "5c7975a4", + "metadata": {}, + "source": [ + "Таким образом, мы можем определить класс и использовать как стандартный декоратор `property`, так и новый только что созданный. В двух видах можем просто его использовать как декоратор с помощью синтаксического сахара, можем использовать как вызов функции." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "39ec49f2", + "metadata": {}, + "outputs": [], + "source": [ + "class Class:\n", + " @property\n", + " def original(self):\n", + " return 'original'\n", + " \n", + " @Property\n", + " def custom_sugar(self):\n", + " return 'custom sugar'\n", + " \n", + " def custom_pure(self):\n", + " return 'custom pure'\n", + "\n", + " custom_pure = Property(custom_pure)" + ] + }, + { + "cell_type": "markdown", + "id": "8508693d", + "metadata": {}, + "source": [ + "И окажется, что они работают идентично, потому что на самом деле `property` реализован именно с помощью дескриптора." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "4c4aa005", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "original\n", + "custom sugar\n", + "custom pure\n" + ] + } + ], + "source": [ + "obj = Class()\n", + "\n", + "print(obj.original)\n", + "print(obj.custom_sugar)\n", + "print(obj.custom_pure)" + ] + }, + { + "cell_type": "markdown", + "id": "e112eb48", + "metadata": {}, + "source": [ + "Точно так же реализованы `StaticMethod` и `ClassMethod`. И мы можем точно так же написать свою реализацию `ClassMethod` и `StaticMethod`. `StaticMethod` просто сохраняет функцию. И когда она вызывается, когда мы пытаемся получить соответствующий атрибут, мы просто ее возвращаем, потому что это статический метод, нам не нужно передавать туда ни `self`, ни `class`." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "6e662007", + "metadata": {}, + "outputs": [], + "source": [ + "class StaticMethod:\n", + " def __init__(self, func):\n", + " self.func = func\n", + " \n", + " def __get__(self, obj, obj_type=None):\n", + " return self.func" + ] + }, + { + "cell_type": "markdown", + "id": "7984e41f", + "metadata": {}, + "source": [ + "А `ClassMethod`, когда мы вызываем нашу функцию от объекта, то есть наш `obj_type` равен нулю, равен `None`, то мы передаем соответствующий `obj_type` первым значением. Как видите, как и ожидается от `ClassMethod`. `ClassMethod` принимает первым значением `class`. Именно это и делает наша реализация `ClassMethod`." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "1b6d514c", + "metadata": {}, + "outputs": [], + "source": [ + "class ClassMethod:\n", + " def __init__(self, func):\n", + " self.func = func\n", + " \n", + " def __get__(self, obj, obj_type=None):\n", + " if obj_type is None:\n", + " obj_type = type(obj)\n", + " \n", + " def new_func(*args, **kwargs):\n", + " return self.func(obj_type, *args, **kwargs)\n", + " \n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "id": "fbd29ba8", + "metadata": {}, + "source": [ + "## `__slots__` ##" + ] + }, + { + "cell_type": "markdown", + "id": "e948fdc7", + "metadata": {}, + "source": [ + "На самом деле с помощью дескрипторов в Python реализовано очень много чего, и, например, есть такая конструкция `__slots__`, которая работает тоже с помощью дескрипторов. `__slots__` позволяет вам определить класс, у которого есть жестко заданный набор атрибутов. Как вы знаете, когда мы создаем класс, у класса создается соответствующий словарь, в который мы записываем атрибуты, которые добавляются в объект. Очень часто это бывает излишне. У вас может быть огромное количество, например, объектов, и вы не хотите создавать каждый раз для каждого объекта словарь. Для этого приходит на помощь конструкция `__slots__`, которая вам позволяет жестко задать количество элементов, которые ваш класс может содержать.\n", + "\n", + "В данном случае мы говорим, что у нас в нашем классе должен быть только атрибут `anakin`. Собственно, он при инициализации и создается. Если мы попытаемся добавить в наш класс, в наш объект какой-то еще один атрибут, у нас ничего не получится, потому что у нас нет, собственно, справочника, нет `dict`, в который мы это записываем. И `__slots__` реализуется с помощью определения дескрипторов для каждого из атрибутов." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "5b70d468", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'Class' object has no attribute 'luke'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mobj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mClass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mluke\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"the chosen too\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m: 'Class' object has no attribute 'luke'" + ] + } + ], + "source": [ + "class Class:\n", + " __slots__ = [\"anakin\"]\n", + " \n", + " def __init__(self):\n", + " self.anakin = \"the chosen one\"\n", + "\n", + "obj = Class()\n", + "obj.luke = \"the chosen too\"" + ] + }, + { + "cell_type": "markdown", + "id": "b48293d5", + "metadata": {}, + "source": [ + "Мы познакомились с вами с дескрипторами, узнали, как они на самом деле работают, и на самом деле с помощью дескрипторов реализована практически вся магия, вся работа с методами и функциями в Python." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/2. Механизм работы классов/Документация.ipynb b/4. Углубленный Python/2. Механизм работы классов/Документация.ipynb new file mode 100644 index 0000000..f413ca8 --- /dev/null +++ b/4. Углубленный Python/2. Механизм работы классов/Документация.ipynb @@ -0,0 +1,42 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "357f2682", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "04c7dd81", + "metadata": {}, + "source": [ + "- [Data model](https://docs.python.org/3/reference/datamodel.html \"3. Data model\")\n", + "- [Дескрипторы](https://docs.python.org/3/howto/descriptor.html \"Descriptor HowTo Guide\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/2. Механизм работы классов/Метаклассы.ipynb b/4. Углубленный Python/2. Механизм работы классов/Метаклассы.ipynb new file mode 100644 index 0000000..e824fa4 --- /dev/null +++ b/4. Углубленный Python/2. Механизм работы классов/Метаклассы.ipynb @@ -0,0 +1,508 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "90fa98ae", + "metadata": {}, + "source": [ + "# Метаклассы #" + ] + }, + { + "cell_type": "markdown", + "id": "a49613fc", + "metadata": {}, + "source": [ + "В этой лекции мы поговорим с вами о мета-классах. Как вы уже знаете, всё в Python'е является объектом, и классы не исключение, а значит эти классы кто-то создаёт. Давайте определим класс с названием Class и его объект. Тип нашего объекта является Class, потому что Class создал наш объект." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "341462e8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.Class" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "class Class:\n", + " ...\n", + " \n", + "obj = Class()\n", + "type(obj)" + ] + }, + { + "cell_type": "markdown", + "id": "5c17b22a", + "metadata": {}, + "source": [ + "Однако, у класса тоже есть тип. Этот тип `type`, потому что `type` создал наш класс." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "03dc5e19", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "type" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(Class)" + ] + }, + { + "cell_type": "markdown", + "id": "9e88bd36", + "metadata": {}, + "source": [ + "В данном случае `type` является мета-классом. Он создаёт другие классы. Типом самого `type`, кстати, является он же сам." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7c5cab2a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "type" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(type)" + ] + }, + { + "cell_type": "markdown", + "id": "27b06d18", + "metadata": {}, + "source": [ + "Это рекурсивное замыкание, которое реализовано с помощью С внутри. Очень важно понимать разницу между созданием и наследованием." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "fd58b7cb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "issubclass(Class, type)" + ] + }, + { + "cell_type": "markdown", + "id": "75d5b828", + "metadata": {}, + "source": [ + "В данном случае класс не является `subclass`'ом `type`. `Type` его создаёт, но класс не наследуется от него. Класс наследуется от класса объекта." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "410a1c95", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "issubclass(Class, object)" + ] + }, + { + "cell_type": "markdown", + "id": "114f8dd7", + "metadata": {}, + "source": [ + "Чтобы понять, как вообще классы задаются, можно написать простую функцию, которая класс возвращает." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "21dc42eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n" + ] + } + ], + "source": [ + "def dummy_factory():\n", + " class Class:\n", + " pass\n", + " \n", + " return Class\n", + "\n", + "Dummy = dummy_factory()\n", + "print(Dummy() is Dummy())" + ] + }, + { + "cell_type": "markdown", + "id": "d2107543", + "metadata": {}, + "source": [ + "В данном случае мы определяем функцию, которая возвращает какой-то новый класс — `dummy_factory`. Классы можно создавать на лету. В данном случае мы создаём два разных объекта и просто их возвращаем.\n", + "\n", + "Однако, на самом деле, Python работает, конечно, не так. Для создания классов используется мета-класс `type`, и вы можете на лету создать `type`, просто вызвав его и передав название класса." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8186063e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "<__main__.NewClass object at 0x7f993b956d68>\n" + ] + } + ], + "source": [ + "NewClass = type(\"NewClass\", (), {})\n", + "\n", + "print(NewClass)\n", + "print(NewClass())" + ] + }, + { + "cell_type": "markdown", + "id": "05936a27", + "metadata": {}, + "source": [ + "В данном случае мы создаём класс `NewClass` без родителей и без каких-то атрибутов. `NewClass` действительно является классическим классом. Вы можете его вывести, можете создать какой-то объект этого класса. Это настоящий класс, мы создали его на лету без литерала `class`.\n", + "\n", + "Однако, чаще всего классы создаются всё-таки по-другому. Они создаются с помощью мета-классов, и в данном случае давайте определим свой собственный мета-класс, который будет управлять поведением при создании класса." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c6a34b73", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Creating A\n" + ] + } + ], + "source": [ + "class Meta(type):\n", + " def __new__(cls, name, parents, attrs):\n", + " print(f\"Creating {name}\")\n", + " \n", + " if \"class_id\" not in attrs:\n", + " attrs[\"class_id\"] = name.lower()\n", + " \n", + " return super().__new__(cls, name, parents, attrs)\n", + "\n", + "class A(metaclass=Meta):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "596e5a1e", + "metadata": {}, + "source": [ + "Мы определим класс `Meta`. Для того чтобы он бы мета-классом, он должен наследоваться от другого мета-класса. В данном случае это мета-класс `type`, базовый мета-класс. И, как вы уже знаете, метод `__new__` управляет поведением при создании объекта. В данном случае объектом является другой класс, поэтому мы можем изменять поведение при создании другого класса. Метод `__new__` принимает название класса, его родителей и какие-то атрибуты. Мы можем определить какой-то новый класс `A` и указать, что его мета-классом является наш мета-класс. Именно этот мета-класс и будет управлять поведением при создании нового класса. В данном случае мы выводим строчку о том, что у нас класс создаётся. При определении `class` у нас вызывается мета-класс и функция `__new__`, метод `__new__`. Мы выводим, что у нас наш класс создаётся. Записываем в какой-то атрибут нашего класса, в данном случае в атрибут `class_id`, значение." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f83fe579", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A.class_id: 'a'\n" + ] + } + ], + "source": [ + "print(f\"A.class_id: '{A.class_id}'\")" + ] + }, + { + "cell_type": "markdown", + "id": "c17782d2", + "metadata": {}, + "source": [ + "Таким образом, мы можем переопределить поведение при создании класса. Например, добавить ему какой-то атрибут или сделать что-нибудь другое." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1fa3f00a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initializing — Base\n", + "Initializing — A\n", + "Initializing — B\n" + ] + } + ], + "source": [ + "class Meta(type):\n", + " def __init__(cls, name, bases, attrs):\n", + " print(f\"Initializing — {name}\")\n", + " \n", + " if not hasattr(cls, \"registry\"):\n", + " cls.registry = {}\n", + " else:\n", + " cls.registry[name.lower()] = cls\n", + " \n", + " super().__init__(name, bases, attrs)\n", + "\n", + "class Base(metaclass=Meta): pass\n", + "\n", + "class A(Base): pass\n", + "\n", + "class B(Base): pass" + ] + }, + { + "cell_type": "markdown", + "id": "b4ba7f87", + "metadata": {}, + "source": [ + "Например, мы можем определить мета-класс, который переопределяет функцию `__init__`, и в данном случае наш мета-класс будет логировать, запоминать все созданные подклассы. Давайте определим функцию `__init__`, которая будет вызываться при инициализации нашего объекта. В данном случае нашим объектом является класс. При инициализации класса у нас она будет вызываться. Наш `__init__` принимает те же самые аргументы, однако, делает немного другое. Он записывает свой собственный атрибут значения созданных классов. В данном случае, у нас вначале создаётся класс `Base`, мета-классом которого является `Meta`, и у него создаётся `registry`, в который мы потом будем записывать все его подклассы. Каждый раз, когда у нас создаётся какой-то класс, который наследуется от `Base`, мы записываем в наш `registry` соответствующее значение, то есть название созданного класса и ссылку на него, то есть объект `class`. И мы можем вывести теперь все подклассы нашего `Base`, просто обратившись к `registry`, ну или написав обращение к методу `subclasses`." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "d4c2e1a5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'a': , 'b': }\n", + "[, ]\n" + ] + } + ], + "source": [ + "print(Base.registry)\n", + "print(Base.__subclasses__())" + ] + }, + { + "cell_type": "markdown", + "id": "b9aae82f", + "metadata": {}, + "source": [ + "## Абстрактные методы ##" + ] + }, + { + "cell_type": "markdown", + "id": "2245e2a3", + "metadata": {}, + "source": [ + "Очень часто при работе с объектно-ориентированной парадигмой в Python'е возникают вопросы про абстрактные методы, потому что абстрактные методы являются центральным понятием, например, в языке программирования C++. В Python'е есть абстрактные методы, вы можете их использовать с помощью стандартной библиотеки `abc`.\n", + "\n", + "В данном случае здесь также работают мета-классы и мы можем определить абстрактный какой-то класс с методом `abstractmethod`." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "a7a09dc8", + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABCMeta, abstractmethod\n", + "\n", + "class Sender(metaclass=ABCMeta):\n", + " @abstractmethod\n", + " def send(self):\n", + " \"\"\"Do something\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "49c0d57b", + "metadata": {}, + "source": [ + "О чём говорит наш декоратор `abstractmethod`? Что у нас не получится создать какой-то класс, не определив этот метод. То есть у нас метод абстрактный и мы обязаны его переопределить в классе, который наследуется от нашего класса. В данном случае у нас `Child` не переопределяет метод `send`, и поэтому вызывается ошибка." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "05e82b55", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "Can't instantiate abstract class Child with abstract methods send", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mChild\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mSender\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mChild\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m: Can't instantiate abstract class Child with abstract methods send" + ] + } + ], + "source": [ + "class Child(Sender): pass\n", + "\n", + "Child()" + ] + }, + { + "cell_type": "markdown", + "id": "3bdd199c", + "metadata": {}, + "source": [ + "Если мы переопределим метод `send`, то всё будет, как мы хотели, у нас абстрактный метод переопределён, всё работает." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "c0599a42", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.Child at 0x7f993b90fef0>" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "class Child(Sender):\n", + " def send(self):\n", + " print(\"Sending\")\n", + "\n", + "Child()" + ] + }, + { + "cell_type": "markdown", + "id": "6ff82354", + "metadata": {}, + "source": [ + "Ну и, на самом деле, абстрактные методы используются в Python'е довольно редко, чаще всего вызывается просто исключение `NotImplementedError`, которое говорит о том, что этот метод нужно реализовать. Программист, когда видит в определении класса, что вызывается в методе `raise NotImplementedError`, что этот класс нужно в потомке переопределить." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "a7ef9b3f", + "metadata": {}, + "outputs": [], + "source": [ + "class PythonWay:\n", + " def send(self):\n", + " raise NotImplementedError" + ] + }, + { + "cell_type": "markdown", + "id": "66529f93", + "metadata": {}, + "source": [ + "Что ж, мы с вами разобрали, как работают мета-классы. На самом деле мета-класс — это просто класс, который создаёт другие классы, потому что классы тоже являются объектом. Мы посмотрели, как можно написать свой собственный мета-класс и переопределить поведение при создании другого класса. Также вы можете определять собственные абстрактные методы, если вам необходимо сказать о том, что в классе, который наследуется от вашего другого класса, обязательно должен реализован быть какой-то метод, какой-то интерфейс. На этом всё." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/3. Отладка и тестирование/apophis_fixture.txt b/4. Углубленный Python/3. Отладка и тестирование/apophis_fixture.txt new file mode 100644 index 0000000..c7f065d --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/apophis_fixture.txt @@ -0,0 +1 @@ +{"links":{"self":"http://www.neowsapp.com/rest/v1/neo/2099942?api_key=DEMO_KEY"},"id":"2099942","neo_reference_id":"2099942","name":"99942 Apophis (2004 MN4)","name_limited":"Apophis","designation":"99942","nasa_jpl_url":"http://ssd.jpl.nasa.gov/sbdb.cgi?sstr=2099942","absolute_magnitude_h":19.7,"estimated_diameter":{"kilometers":{"estimated_diameter_min":0.3051792326,"estimated_diameter_max":0.6824015094},"meters":{"estimated_diameter_min":305.1792325939,"estimated_diameter_max":682.4015094011},"miles":{"estimated_diameter_min":0.1896295249,"estimated_diameter_max":0.4240245083},"feet":{"estimated_diameter_min":1001.2442334633,"estimated_diameter_max":2238.8501681036}},"is_potentially_hazardous_asteroid":true,"close_approach_data":[{"close_approach_date":"1905-12-26","close_approach_date_full":"1905-Dec-26 05:03","epoch_date_close_approach":-2020186620000,"relative_velocity":{"kilometers_per_second":"14.1571846744","kilometers_per_hour":"50965.8648276884","miles_per_hour":"31668.2063980712"},"miss_distance":{"astronomical":"0.2762060271","lunar":"107.4441445419","kilometers":"41319833.335322277","miles":"25674953.8711383426"},"orbiting_body":"Earth"},{"close_approach_date":"1906-09-12","close_approach_date_full":"1906-Sep-12 20:32","epoch_date_close_approach":-1997666880000,"relative_velocity":{"kilometers_per_second":"7.7164631891","kilometers_per_hour":"27779.2674808387","miles_per_hour":"17260.9565077467"},"miss_distance":{"astronomical":"0.2518211269","lunar":"97.9584183641","kilometers":"37671904.205239703","miles":"23408235.8188177814"},"orbiting_body":"Earth"},{"close_approach_date":"1907-04-13","close_approach_date_full":"1907-Apr-13 01:12","epoch_date_close_approach":-1979333280000,"relative_velocity":{"kilometers_per_second":"5.0966109604","kilometers_per_hour":"18347.7994572688","miles_per_hour":"11400.6090572124"},"miss_distance":{"astronomical":"0.0286139788","lunar":"11.1308377532","kilometers":"4280590.280705156","miles":"2659835.4622209128"},"orbiting_body":"Earth"},{"close_approach_date":"1912-10-15","close_approach_date_full":"1912-Oct-15 11:50","epoch_date_close_approach":-1805458200000,"relative_velocity":{"kilometers_per_second":"3.5452628846","kilometers_per_hour":"12762.9463846831","miles_per_hour":"7930.3985466382"},"miss_distance":{"astronomical":"0.1450362338","lunar":"56.4190949482","kilometers":"21697111.649302006","miles":"13481960.0120834428"},"orbiting_body":"Venus"},{"close_approach_date":"1912-12-15","close_approach_date_full":"1912-Dec-15 21:07","epoch_date_close_approach":-1800154380000,"relative_velocity":{"kilometers_per_second":"8.3895000392","kilometers_per_hour":"30202.2001410166","miles_per_hour":"18766.4726376223"},"miss_distance":{"astronomical":"0.1256038555","lunar":"48.8598997895","kilometers":"18790069.246587785","miles":"11675607.624710033"},"orbiting_body":"Venus"},{"close_approach_date":"1914-11-06","close_approach_date_full":"1914-Nov-06 19:56","epoch_date_close_approach":-1740456240000,"relative_velocity":{"kilometers_per_second":"4.1172065817","kilometers_per_hour":"14821.9436941276","miles_per_hour":"9209.7794026094"},"miss_distance":{"astronomical":"0.1631073381","lunar":"63.4487545209","kilometers":"24400510.361129847","miles":"15161774.0776000086"},"orbiting_body":"Earth"},{"close_approach_date":"1915-01-28","close_approach_date_full":"1915-Jan-28 21:09","epoch_date_close_approach":-1733280660000,"relative_velocity":{"kilometers_per_second":"4.5036783988","kilometers_per_hour":"16213.2422355892","miles_per_hour":"10074.2782102193"},"miss_distance":{"astronomical":"0.122134768","lunar":"47.510424752","kilometers":"18271101.14574416","miles":"11353135.800063008"},"orbiting_body":"Earth"},{"close_approach_date":"1915-06-29","close_approach_date_full":"1915-Jun-29 18:37","epoch_date_close_approach":-1720156980000,"relative_velocity":{"kilometers_per_second":"6.1415494159","kilometers_per_hour":"22109.5778973792","miles_per_hour":"13738.0318885132"},"miss_distance":{"astronomical":"0.2278575008","lunar":"88.6365678112","kilometers":"34086996.783203296","miles":"21180677.6400096448"},"orbiting_body":"Earth"},{"close_approach_date":"1916-04-03","close_approach_date_full":"1916-Apr-03 20:32","epoch_date_close_approach":-1696044480000,"relative_velocity":{"kilometers_per_second":"17.0360271948","kilometers_per_hour":"61329.6979013502","miles_per_hour":"38107.8892320917"},"miss_distance":{"astronomical":"0.3682227568","lunar":"143.2386523952","kilometers":"55085340.102808016","miles":"34228443.1458859808"},"orbiting_body":"Earth"},{"close_approach_date":"1922-10-16","close_approach_date_full":"1922-Oct-16 22:28","epoch_date_close_approach":-1489800720000,"relative_velocity":{"kilometers_per_second":"4.3053431844","kilometers_per_hour":"15499.2354638901","miles_per_hour":"9630.6221692155"},"miss_distance":{"astronomical":"0.0755351063","lunar":"29.3831563507","kilometers":"11299891.012703581","miles":"7021426.6874121778"},"orbiting_body":"Venus"},{"close_approach_date":"1922-12-19","close_approach_date_full":"1922-Dec-19 04:32","epoch_date_close_approach":-1484335680000,"relative_velocity":{"kilometers_per_second":"8.9782680036","kilometers_per_hour":"32321.7648128706","miles_per_hour":"20083.4876971973"},"miss_distance":{"astronomical":"0.1153514479","lunar":"44.8717132331","kilometers":"17256330.907255973","miles":"10722586.8128115074"},"orbiting_body":"Earth"},{"close_approach_date":"1923-08-17","close_approach_date_full":"1923-Aug-17 11:05","epoch_date_close_approach":-1463489700000,"relative_velocity":{"kilometers_per_second":"8.0693522094","kilometers_per_hour":"29049.6679538895","miles_per_hour":"18050.3339572376"},"miss_distance":{"astronomical":"0.2636241608","lunar":"102.5497985512","kilometers":"39437612.936217496","miles":"24505396.3482376048"},"orbiting_body":"Earth"},{"close_approach_date":"1924-04-13","close_approach_date_full":"1924-Apr-13 06:41","epoch_date_close_approach":-1442769540000,"relative_velocity":{"kilometers_per_second":"9.0684833766","kilometers_per_hour":"32646.5401557401","miles_per_hour":"20285.2904651042"},"miss_distance":{"astronomical":"0.1078608258","lunar":"41.9578612362","kilometers":"16135749.796121046","miles":"10026289.9980645948"},"orbiting_body":"Earth"},{"close_approach_date":"1930-12-30","close_approach_date_full":"1930-Dec-30 00:09","epoch_date_close_approach":-1230940260000,"relative_velocity":{"kilometers_per_second":"17.3869806016","kilometers_per_hour":"62593.130165765","miles_per_hour":"38892.9369077222"},"miss_distance":{"astronomical":"0.3780056083","lunar":"147.0441816287","kilometers":"56548833.849734321","miles":"35137815.9920467898"},"orbiting_body":"Earth"},{"close_approach_date":"1931-10-04","close_approach_date_full":"1931-Oct-04 17:25","epoch_date_close_approach":-1206858900000,"relative_velocity":{"kilometers_per_second":"6.5833261599","kilometers_per_hour":"23699.974175756","miles_per_hour":"14726.2422871524"},"miss_distance":{"astronomical":"0.2222928013","lunar":"86.4718997057","kilometers":"33254529.590813231","miles":"20663406.5128393478"},"orbiting_body":"Earth"},{"close_approach_date":"1932-03-08","close_approach_date_full":"1932-Mar-08 02:27","epoch_date_close_approach":-1193434380000,"relative_velocity":{"kilometers_per_second":"4.5537329994","kilometers_per_hour":"16393.4387979926","miles_per_hour":"10186.2453464527"},"miss_distance":{"astronomical":"0.1143920867","lunar":"44.4985217263","kilometers":"17112812.515175329","miles":"10633408.6192203802"},"orbiting_body":"Earth"},{"close_approach_date":"1932-07-11","close_approach_date_full":"1932-Jul-11 00:09","epoch_date_close_approach":-1182642660000,"relative_velocity":{"kilometers_per_second":"6.5060322435","kilometers_per_hour":"23421.7160767047","miles_per_hour":"14553.3435255502"},"miss_distance":{"astronomical":"0.1068409429","lunar":"41.5611267881","kilometers":"15983177.486631623","miles":"9931485.9610694774"},"orbiting_body":"Venus"},{"close_approach_date":"1932-08-29","close_approach_date_full":"1932-Aug-29 03:25","epoch_date_close_approach":-1178397300000,"relative_velocity":{"kilometers_per_second":"3.2345022332","kilometers_per_hour":"11644.2080394458","miles_per_hour":"7235.258045399"},"miss_distance":{"astronomical":"0.1172411605","lunar":"45.6068114345","kilometers":"17539027.887128135","miles":"10898246.570652863"},"orbiting_body":"Venus"},{"close_approach_date":"1933-03-30","close_approach_date_full":"1933-Mar-30 06:02","epoch_date_close_approach":-1159984680000,"relative_velocity":{"kilometers_per_second":"20.8732314657","kilometers_per_hour":"75143.6332765366","miles_per_hour":"46691.3314656346"},"miss_distance":{"astronomical":"0.4921597338","lunar":"191.4501364482","kilometers":"73626047.876247006","miles":"45749104.7361244428"},"orbiting_body":"Earth"},{"close_approach_date":"1939-12-18","close_approach_date_full":"1939-Dec-18 16:26","epoch_date_close_approach":-947921640000,"relative_velocity":{"kilometers_per_second":"4.7584653343","kilometers_per_hour":"17130.4752036525","miles_per_hour":"10644.2111064029"},"miss_distance":{"astronomical":"0.0598517401","lunar":"23.2823268989","kilometers":"8953692.834753587","miles":"5563566.7414980206"},"orbiting_body":"Earth"},{"close_approach_date":"1940-07-22","close_approach_date_full":"1940-Jul-22 16:11","epoch_date_close_approach":-929173740000,"relative_velocity":{"kilometers_per_second":"7.4811175743","kilometers_per_hour":"26932.0232675188","miles_per_hour":"16734.5119019758"},"miss_distance":{"astronomical":"0.2549627184","lunar":"99.1804974576","kilometers":"38141879.602049808","miles":"23700264.9888230304"},"orbiting_body":"Earth"},{"close_approach_date":"1941-04-08","close_approach_date_full":"1941-Apr-08 11:23","epoch_date_close_approach":-906727020000,"relative_velocity":{"kilometers_per_second":"13.470181221","kilometers_per_hour":"48492.6523956058","miles_per_hour":"30131.4483732585"},"miss_distance":{"astronomical":"0.2529495435","lunar":"98.3973724215","kilometers":"37840712.925072345","miles":"23513128.693374561"},"orbiting_body":"Earth"},{"close_approach_date":"1947-12-23","close_approach_date_full":"1947-Dec-23 01:26","epoch_date_close_approach":-695082840000,"relative_velocity":{"kilometers_per_second":"11.8463545575","kilometers_per_hour":"42646.8764070396","miles_per_hour":"26499.1105096964"},"miss_distance":{"astronomical":"0.2036535413","lunar":"79.2212275657","kilometers":"30466135.996437031","miles":"18930779.0762957878"},"orbiting_body":"Earth"},{"close_approach_date":"1948-08-31","close_approach_date_full":"1948-Aug-31 10:09","epoch_date_close_approach":-673278660000,"relative_velocity":{"kilometers_per_second":"7.9794523549","kilometers_per_hour":"28726.0284774915","miles_per_hour":"17849.2369725835"},"miss_distance":{"astronomical":"0.2591915846","lunar":"100.8255264094","kilometers":"38774508.978084802","miles":"24093362.6549101876"},"orbiting_body":"Earth"},{"close_approach_date":"1949-04-14","close_approach_date_full":"1949-Apr-14 11:30","epoch_date_close_approach":-653747400000,"relative_velocity":{"kilometers_per_second":"6.6900037419","kilometers_per_hour":"24084.0134708444","miles_per_hour":"14964.8693702588"},"miss_distance":{"astronomical":"0.0279609577","lunar":"10.8768125453","kilometers":"4182899.715080099","miles":"2599133.3595354062"},"orbiting_body":"Earth"},{"close_approach_date":"1950-06-26","close_approach_date_full":"1950-Jun-26 04:15","epoch_date_close_approach":-615930300000,"relative_velocity":{"kilometers_per_second":"6.6160807598","kilometers_per_hour":"23817.8907352307","miles_per_hour":"14799.511051566"},"miss_distance":{"astronomical":"0.0912975899","lunar":"35.5147624711","kilometers":"13657924.985173513","miles":"8486641.0550119594"},"orbiting_body":"Venus"},{"close_approach_date":"1955-12-29","close_approach_date_full":"1955-Dec-29 05:19","epoch_date_close_approach":-442089660000,"relative_velocity":{"kilometers_per_second":"15.874112609","kilometers_per_hour":"57146.8053922607","miles_per_hour":"35508.8024949855"},"miss_distance":{"astronomical":"0.3305153397","lunar":"128.5704671433","kilometers":"49444390.821446439","miles":"30723319.7971772982"},"orbiting_body":"Earth"},{"close_approach_date":"1956-09-22","close_approach_date_full":"1956-Sep-22 08:50","epoch_date_close_approach":-418921800000,"relative_velocity":{"kilometers_per_second":"7.3371700258","kilometers_per_hour":"26413.8120929063","miles_per_hour":"16412.5156307284"},"miss_distance":{"astronomical":"0.2413152319","lunar":"93.8716252091","kilometers":"36100244.690796053","miles":"22431651.8813414114"},"orbiting_body":"Earth"},{"close_approach_date":"1957-04-01","close_approach_date_full":"1957-Apr-01 03:20","epoch_date_close_approach":-402439200000,"relative_velocity":{"kilometers_per_second":"4.2812625681","kilometers_per_hour":"15412.5452450281","miles_per_hour":"9576.7562385009"},"miss_distance":{"astronomical":"0.075395985","lunar":"29.329038165","kilometers":"11279078.76255195","miles":"7008494.55483891"},"orbiting_body":"Earth"},{"close_approach_date":"1964-01-04","close_approach_date_full":"1964-Jan-04 02:14","epoch_date_close_approach":-189121560000,"relative_velocity":{"kilometers_per_second":"20.4617126434","kilometers_per_hour":"73662.165516109","miles_per_hour":"45770.80501194"},"miss_distance":{"astronomical":"0.4757846331","lunar":"185.0802222759","kilometers":"71176367.690491497","miles":"44226944.0522247786"},"orbiting_body":"Earth"},{"close_approach_date":"1964-10-24","close_approach_date_full":"1964-Oct-24 21:45","epoch_date_close_approach":-163649700000,"relative_velocity":{"kilometers_per_second":"5.1541620113","kilometers_per_hour":"18554.9832407043","miles_per_hour":"11529.3450030921"},"miss_distance":{"astronomical":"0.1876922503","lunar":"73.0122853667","kilometers":"28078360.860386861","miles":"17447084.4065902418"},"orbiting_body":"Earth"},{"close_approach_date":"1965-02-11","close_approach_date_full":"1965-Feb-11 12:16","epoch_date_close_approach":-154179840000,"relative_velocity":{"kilometers_per_second":"4.6699142323","kilometers_per_hour":"16811.6912362824","miles_per_hour":"10446.1311462334"},"miss_distance":{"astronomical":"0.1258367785","lunar":"48.9505068365","kilometers":"18824914.031261795","miles":"11697259.169910971"},"orbiting_body":"Earth"},{"close_approach_date":"1965-06-18","close_approach_date_full":"1965-Jun-18 04:49","epoch_date_close_approach":-143233860000,"relative_velocity":{"kilometers_per_second":"5.3288286822","kilometers_per_hour":"19183.7832560816","miles_per_hour":"11920.0568793136"},"miss_distance":{"astronomical":"0.2113760062","lunar":"82.2252664118","kilometers":"31621400.296626794","miles":"19648627.0253819972"},"orbiting_body":"Earth"},{"close_approach_date":"1966-04-02","close_approach_date_full":"1966-Apr-02 02:44","epoch_date_close_approach":-118358160000,"relative_velocity":{"kilometers_per_second":"18.7886307422","kilometers_per_hour":"67639.0706719866","miles_per_hour":"42028.2881072686"},"miss_distance":{"astronomical":"0.4249282632","lunar":"165.2970943848","kilometers":"63568363.077519384","miles":"39499549.1979361392"},"orbiting_body":"Earth"},{"close_approach_date":"1968-03-20","close_approach_date_full":"1968-Mar-20 02:08","epoch_date_close_approach":-56325120000,"relative_velocity":{"kilometers_per_second":"4.2796576486","kilometers_per_hour":"15406.7675349637","miles_per_hour":"9573.1661941557"},"miss_distance":{"astronomical":"0.0841965117","lunar":"32.7524430513","kilometers":"12595618.811750079","miles":"7826554.6074619302"},"orbiting_body":"Venus"},{"close_approach_date":"1968-04-25","close_approach_date_full":"1968-Apr-25 16:17","epoch_date_close_approach":-53163780000,"relative_velocity":{"kilometers_per_second":"3.264176993","kilometers_per_hour":"11751.0371748878","miles_per_hour":"7301.6375156962"},"miss_distance":{"astronomical":"0.0858944401","lunar":"33.4129371989","kilometers":"12849625.283802587","miles":"7984386.9104142206"},"orbiting_body":"Venus"},{"close_approach_date":"1972-12-24","close_approach_date_full":"1972-Dec-24 11:52","epoch_date_close_approach":94045920000,"relative_velocity":{"kilometers_per_second":"4.0568812007","kilometers_per_hour":"14604.7723226102","miles_per_hour":"9074.8375579019"},"miss_distance":{"astronomical":"0.0792377466","lunar":"30.8234834274","kilometers":"11853798.114959742","miles":"7365608.6008267596"},"orbiting_body":"Earth"},{"close_approach_date":"1973-07-16","close_approach_date_full":"1973-Jul-16 05:35","epoch_date_close_approach":111648900000,"relative_velocity":{"kilometers_per_second":"7.2034056607","kilometers_per_hour":"25932.2603785673","miles_per_hour":"16113.2981224492"},"miss_distance":{"astronomical":"0.2505063412","lunar":"97.4469667268","kilometers":"37475215.065013244","miles":"23286018.8543570072"},"orbiting_body":"Earth"},{"close_approach_date":"1974-04-06","close_approach_date_full":"1974-Apr-06 17:35","epoch_date_close_approach":134501700000,"relative_velocity":{"kilometers_per_second":"14.9772383758","kilometers_per_hour":"53918.0581527896","miles_per_hour":"33502.5845227709"},"miss_distance":{"astronomical":"0.3016939562","lunar":"117.3589489618","kilometers":"45132773.239393294","miles":"28044204.8638996972"},"orbiting_body":"Earth"},{"close_approach_date":"1976-04-29","close_approach_date_full":"1976-Apr-29 04:34","epoch_date_close_approach":199600440000,"relative_velocity":{"kilometers_per_second":"9.1113794173","kilometers_per_hour":"32800.9659023928","miles_per_hour":"20381.2446186284"},"miss_distance":{"astronomical":"0.1351015752","lunar":"52.5545127528","kilometers":"20210907.883564824","miles":"12558475.8145856112"},"orbiting_body":"Venus"},{"close_approach_date":"1980-12-18","close_approach_date_full":"1980-Dec-18 01:52","epoch_date_close_approach":345952320000,"relative_velocity":{"kilometers_per_second":"7.3503431259","kilometers_per_hour":"26461.2352533041","miles_per_hour":"16441.9825383655"},"miss_distance":{"astronomical":"0.0721270349","lunar":"28.0574165761","kilometers":"10790050.790455663","miles":"6704626.6635196294"},"orbiting_body":"Earth"},{"close_approach_date":"1981-08-06","close_approach_date_full":"1981-Aug-06 17:19","epoch_date_close_approach":365966340000,"relative_velocity":{"kilometers_per_second":"7.9804296037","kilometers_per_hour":"28729.5465733753","miles_per_hour":"17851.4229805509"},"miss_distance":{"astronomical":"0.2639856426","lunar":"102.6904149714","kilometers":"39491689.843541262","miles":"24538998.1803109356"},"orbiting_body":"Earth"},{"close_approach_date":"1982-04-11","close_approach_date_full":"1982-Apr-11 12:17","epoch_date_close_approach":387375420000,"relative_velocity":{"kilometers_per_second":"11.248978562","kilometers_per_hour":"40496.3228231521","miles_per_hour":"25162.840145308"},"miss_distance":{"astronomical":"0.179530917","lunar":"69.837526713","kilometers":"26857442.78234679","miles":"16688441.091654102"},"orbiting_body":"Earth"},{"close_approach_date":"1988-12-23","close_approach_date_full":"1988-Dec-23 10:02","epoch_date_close_approach":598874520000,"relative_velocity":{"kilometers_per_second":"12.0824833601","kilometers_per_hour":"43496.940096428","miles_per_hour":"27027.30702825"},"miss_distance":{"astronomical":"0.2115390957","lunar":"82.2887082273","kilometers":"31645798.138446159","miles":"19663787.1413046342"},"orbiting_body":"Earth"},{"close_approach_date":"1989-09-01","close_approach_date_full":"1989-Sep-01 09:18","epoch_date_close_approach":620644680000,"relative_velocity":{"kilometers_per_second":"8.0229703783","kilometers_per_hour":"28882.6933619268","miles_per_hour":"17946.5824392483"},"miss_distance":{"astronomical":"0.2603294739","lunar":"101.2681653471","kilometers":"38944734.793660593","miles":"24199136.0719304634"},"orbiting_body":"Earth"},{"close_approach_date":"1990-04-14","close_approach_date_full":"1990-Apr-14 20:44","epoch_date_close_approach":640125840000,"relative_velocity":{"kilometers_per_second":"6.8446883801","kilometers_per_hour":"24640.8781684028","miles_per_hour":"15310.8834374723"},"miss_distance":{"astronomical":"0.0329293163","lunar":"12.8095040407","kilometers":"4926155.579036281","miles":"3060971.1376954378"},"orbiting_body":"Earth"},{"close_approach_date":"1994-01-21","close_approach_date_full":"1994-Jan-21 12:17","epoch_date_close_approach":759154620000,"relative_velocity":{"kilometers_per_second":"4.0452467747","kilometers_per_hour":"14562.8883889347","miles_per_hour":"9048.8125103356"},"miss_distance":{"astronomical":"0.0826533271","lunar":"32.1521442419","kilometers":"12364761.682573277","miles":"7683106.6391621426"},"orbiting_body":"Venus"},{"close_approach_date":"1994-02-26","close_approach_date_full":"1994-Feb-26 12:00","epoch_date_close_approach":762264000000,"relative_velocity":{"kilometers_per_second":"3.3807874029","kilometers_per_hour":"12170.834650403","miles_per_hour":"7562.483341524"},"miss_distance":{"astronomical":"0.0828785989","lunar":"32.2397749721","kilometers":"12398461.864024343","miles":"7704046.9609002134"},"orbiting_body":"Venus"},{"close_approach_date":"1996-12-25","close_approach_date_full":"1996-Dec-25 15:25","epoch_date_close_approach":851527500000,"relative_velocity":{"kilometers_per_second":"13.0620137592","kilometers_per_hour":"47023.2495329461","miles_per_hour":"29218.4185778464"},"miss_distance":{"astronomical":"0.2426582783","lunar":"94.3940702587","kilometers":"36301161.571547221","miles":"22556495.8419488098"},"orbiting_body":"Earth"},{"close_approach_date":"1997-09-05","close_approach_date_full":"1997-Sep-05 07:01","epoch_date_close_approach":873442860000,"relative_velocity":{"kilometers_per_second":"8.0062555442","kilometers_per_hour":"28822.5199590839","miles_per_hour":"17909.1930267985"},"miss_distance":{"astronomical":"0.2598285823","lunar":"101.0733185147","kilometers":"38869802.477199701","miles":"24152575.2895338338"},"orbiting_body":"Earth"},{"close_approach_date":"1998-04-14","close_approach_date_full":"1998-Apr-14 19:46","epoch_date_close_approach":892583160000,"relative_velocity":{"kilometers_per_second":"6.5851913172","kilometers_per_hour":"23706.6887420749","miles_per_hour":"14730.4144575409"},"miss_distance":{"astronomical":"0.0243870961","lunar":"9.4865803829","kilometers":"3648257.632045307","miles":"2266922.1739749566"},"orbiting_body":"Earth"},{"close_approach_date":"2002-01-13","close_approach_date_full":"2002-Jan-13 16:37","epoch_date_close_approach":1010939820000,"relative_velocity":{"kilometers_per_second":"7.7377951573","kilometers_per_hour":"27856.0625662723","miles_per_hour":"17308.6739873596"},"miss_distance":{"astronomical":"0.1184221761","lunar":"46.0662265029","kilometers":"17715705.305324907","miles":"11008028.8276494366"},"orbiting_body":"Venus"},{"close_approach_date":"2002-03-13","close_approach_date_full":"2002-Mar-13 22:05","epoch_date_close_approach":1016057100000,"relative_velocity":{"kilometers_per_second":"3.6057707843","kilometers_per_hour":"12980.7748233173","miles_per_hour":"8065.7486672996"},"miss_distance":{"astronomical":"0.1362414271","lunar":"52.9979151419","kilometers":"20381427.299920277","miles":"12664431.6666707426"},"orbiting_body":"Venus"},{"close_approach_date":"2004-12-21","close_approach_date_full":"2004-Dec-21 09:25","epoch_date_close_approach":1103621100000,"relative_velocity":{"kilometers_per_second":"8.2257858751","kilometers_per_hour":"29612.8291503897","miles_per_hour":"18400.2604240297"},"miss_distance":{"astronomical":"0.0963838495","lunar":"37.4933174555","kilometers":"14418818.587600565","miles":"8959438.415655197"},"orbiting_body":"Earth"},{"close_approach_date":"2005-08-08","close_approach_date_full":"2005-Aug-08 16:24","epoch_date_close_approach":1123518240000,"relative_velocity":{"kilometers_per_second":"8.0992607322","kilometers_per_hour":"29157.3386358871","miles_per_hour":"18117.2363318378"},"miss_distance":{"astronomical":"0.2678906757","lunar":"104.2094728473","kilometers":"40075874.477580759","miles":"24901993.6795781142"},"orbiting_body":"Earth"},{"close_approach_date":"2006-04-10","close_approach_date_full":"2006-Apr-10 23:49","epoch_date_close_approach":1144712940000,"relative_velocity":{"kilometers_per_second":"11.9282390692","kilometers_per_hour":"42941.6606489483","miles_per_hour":"26682.2779737868"},"miss_distance":{"astronomical":"0.2028198253","lunar":"78.8969120417","kilometers":"30341413.858652111","miles":"18853280.3335406918"},"orbiting_body":"Earth"},{"close_approach_date":"2013-01-09","close_approach_date_full":"2013-Jan-09 11:43","epoch_date_close_approach":1357731780000,"relative_velocity":{"kilometers_per_second":"4.0874600669","kilometers_per_hour":"14714.8562409584","miles_per_hour":"9143.2394237222"},"miss_distance":{"astronomical":"0.0966611201","lunar":"37.6011757189","kilometers":"14460297.678774187","miles":"8985212.3277583006"},"orbiting_body":"Earth"},{"close_approach_date":"2013-07-08","close_approach_date_full":"2013-Jul-08 07:36","epoch_date_close_approach":1373268960000,"relative_velocity":{"kilometers_per_second":"6.7274173947","kilometers_per_hour":"24218.7026207543","miles_per_hour":"15048.5599701015"},"miss_distance":{"astronomical":"0.2433074124","lunar":"94.6465834236","kilometers":"36398270.650251588","miles":"22616836.6254163944"},"orbiting_body":"Earth"},{"close_approach_date":"2014-04-04","close_approach_date_full":"2014-Apr-04 12:22","epoch_date_close_approach":1396614120000,"relative_velocity":{"kilometers_per_second":"17.1948199807","kilometers_per_hour":"61901.3519305862","miles_per_hour":"38463.092814869"},"miss_distance":{"astronomical":"0.3736867953","lunar":"145.3641633717","kilometers":"55902748.624006011","miles":"34736357.2486685118"},"orbiting_body":"Earth"},{"close_approach_date":"2016-04-24","close_approach_date_full":"2016-Apr-24 02:50","epoch_date_close_approach":1461466200000,"relative_velocity":{"kilometers_per_second":"6.0890660641","kilometers_per_hour":"21920.6378308627","miles_per_hour":"13620.6318788491"},"miss_distance":{"astronomical":"0.0782418269","lunar":"30.4360706641","kilometers":"11704810.649148703","miles":"7273032.0824019814"},"orbiting_body":"Venus"},{"close_approach_date":"2020-01-05","close_approach_date_full":"2020-Jan-05 04:06","epoch_date_close_approach":1578197160000,"relative_velocity":{"kilometers_per_second":"19.5813214928","kilometers_per_hour":"70492.7573740987","miles_per_hour":"43801.458047256"},"miss_distance":{"astronomical":"0.4477057858","lunar":"174.1575506762","kilometers":"66975831.942356246","miles":"41616852.1699023548"},"orbiting_body":"Earth"},{"close_approach_date":"2020-10-12","close_approach_date_full":"2020-Oct-12 08:38","epoch_date_close_approach":1602491880000,"relative_velocity":{"kilometers_per_second":"6.2682986323","kilometers_per_hour":"22565.8750764261","miles_per_hour":"14021.5572106827"},"miss_distance":{"astronomical":"0.2162763593","lunar":"84.1315037677","kilometers":"32354482.682634691","miles":"20104143.2974770958"},"orbiting_body":"Earth"},{"close_approach_date":"2021-03-06","close_approach_date_full":"2021-Mar-06 01:15","epoch_date_close_approach":1614993300000,"relative_velocity":{"kilometers_per_second":"4.5845285166","kilometers_per_hour":"16504.3026595824","miles_per_hour":"10255.1318386722"},"miss_distance":{"astronomical":"0.1126513868","lunar":"43.8213894652","kilometers":"16852407.517826116","miles":"10471600.4570117608"},"orbiting_body":"Earth"},{"close_approach_date":"2024-03-07","close_approach_date_full":"2024-Mar-07 15:46","epoch_date_close_approach":1709826360000,"relative_velocity":{"kilometers_per_second":"8.0675410467","kilometers_per_hour":"29043.1477679615","miles_per_hour":"18046.2825672648"},"miss_distance":{"astronomical":"0.1244336275","lunar":"48.4046810975","kilometers":"18615005.630373425","miles":"11566828.137766265"},"orbiting_body":"Venus"},{"close_approach_date":"2024-05-07","close_approach_date_full":"2024-May-07 14:35","epoch_date_close_approach":1715092500000,"relative_velocity":{"kilometers_per_second":"3.7041213969","kilometers_per_hour":"13334.837028831","miles_per_hour":"8285.74915272"},"miss_distance":{"astronomical":"0.1415079919","lunar":"55.0466088491","kilometers":"21169294.176217253","miles":"13153989.4425059714"},"orbiting_body":"Venus"},{"close_approach_date":"2027-12-29","close_approach_date_full":"2027-Dec-29 14:23","epoch_date_close_approach":1830090180000,"relative_velocity":{"kilometers_per_second":"14.6855205711","kilometers_per_hour":"52867.8740560118","miles_per_hour":"32850.0409655258"},"miss_distance":{"astronomical":"0.2936124312","lunar":"114.2152357368","kilometers":"43923794.313041544","miles":"27292980.1938155472"},"orbiting_body":"Earth"},{"close_approach_date":"2028-09-12","close_approach_date_full":"2028-Sep-12 04:11","epoch_date_close_approach":1852344660000,"relative_velocity":{"kilometers_per_second":"7.894359271","kilometers_per_hour":"28419.6933754921","miles_per_hour":"17658.8922532328"},"miss_distance":{"astronomical":"0.2565657763","lunar":"99.8040869807","kilometers":"38381693.649376481","miles":"23849278.5279821978"},"orbiting_body":"Earth"},{"close_approach_date":"2029-04-13","close_approach_date_full":"2029-Apr-13 21:46","epoch_date_close_approach":1870811160000,"relative_velocity":{"kilometers_per_second":"7.4225359949","kilometers_per_hour":"26721.1295815929","miles_per_hour":"16603.4707669626"},"miss_distance":{"astronomical":"0.0002540914","lunar":"0.0988415546","kilometers":"38011.532225318","miles":"23619.2708846684"},"orbiting_body":"Earth"},{"close_approach_date":"2029-04-14","close_approach_date_full":"2029-Apr-14 14:32","epoch_date_close_approach":1870871520000,"relative_velocity":{"kilometers_per_second":"6.3957364555","kilometers_per_hour":"23024.6512397237","miles_per_hour":"14306.6228772605"},"miss_distance":{"astronomical":"0.0006415081","lunar":"0.2495466509","kilometers":"95968.245347747","miles":"59631.9024910286"},"orbiting_body":"Moon"},{"close_approach_date":"2029-11-26","close_approach_date_full":"2029-Nov-26 21:39","epoch_date_close_approach":1890423540000,"relative_velocity":{"kilometers_per_second":"6.2625751352","kilometers_per_hour":"22545.2704865853","miles_per_hour":"14008.7543198452"},"miss_distance":{"astronomical":"0.2999606949","lunar":"116.6847103161","kilometers":"44873481.040759863","miles":"27883088.1627495894"},"orbiting_body":"Earth"},{"close_approach_date":"2036-03-27","close_approach_date_full":"2036-Mar-27 08:30","epoch_date_close_approach":2090219400000,"relative_velocity":{"kilometers_per_second":"15.0076735383","kilometers_per_hour":"54027.6247378084","miles_per_hour":"33570.6649377788"},"miss_distance":{"astronomical":"0.3097564953","lunar":"120.4952766717","kilometers":"46338911.915545011","miles":"28793664.6843467118"},"orbiting_body":"Earth"},{"close_approach_date":"2036-12-31","close_approach_date_full":"2036-Dec-31 15:56","epoch_date_close_approach":2114351760000,"relative_velocity":{"kilometers_per_second":"6.8393946795","kilometers_per_hour":"24621.8208463152","miles_per_hour":"15299.0419586452"},"miss_distance":{"astronomical":"0.3300130958","lunar":"128.3750942662","kilometers":"49369256.203785946","miles":"30676633.3106442148"},"orbiting_body":"Earth"},{"close_approach_date":"2037-09-24","close_approach_date_full":"2037-Sep-24 07:43","epoch_date_close_approach":2137390980000,"relative_velocity":{"kilometers_per_second":"12.3673557244","kilometers_per_hour":"44522.4806077662","miles_per_hour":"27664.5380198645"},"miss_distance":{"astronomical":"0.223120233","lunar":"86.793770637","kilometers":"33378311.61070371","miles":"20740321.093422798"},"orbiting_body":"Earth"},{"close_approach_date":"2044-02-06","close_approach_date_full":"2044-Feb-06 14:46","epoch_date_close_approach":2338382760000,"relative_velocity":{"kilometers_per_second":"5.7815095324","kilometers_per_hour":"20813.4343165659","miles_per_hour":"12932.6586729795"},"miss_distance":{"astronomical":"0.2796843353","lunar":"108.7972064317","kilometers":"41840180.833245811","miles":"25998282.8133857518"},"orbiting_body":"Earth"},{"close_approach_date":"2044-08-24","close_approach_date_full":"2044-Aug-24 06:50","epoch_date_close_approach":2355634200000,"relative_velocity":{"kilometers_per_second":"3.9549299019","kilometers_per_hour":"14237.7476470132","miles_per_hour":"8846.7826976677"},"miss_distance":{"astronomical":"0.0803256174","lunar":"31.2466651686","kilometers":"12016541.269474938","miles":"7466732.5078646244"},"orbiting_body":"Earth"},{"close_approach_date":"2051-04-20","close_approach_date_full":"2051-Apr-20 01:55","epoch_date_close_approach":2565568500000,"relative_velocity":{"kilometers_per_second":"4.6947515805","kilometers_per_hour":"16901.1056896551","miles_per_hour":"10501.6898103304"},"miss_distance":{"astronomical":"0.04145533","lunar":"16.12612337","kilometers":"6201629.0681471","miles":"3853513.61314598"},"orbiting_body":"Earth"},{"close_approach_date":"2051-11-21","close_approach_date_full":"2051-Nov-21 02:37","epoch_date_close_approach":2584147020000,"relative_velocity":{"kilometers_per_second":"6.0249147992","kilometers_per_hour":"21689.6932772598","miles_per_hour":"13477.1319144174"},"miss_distance":{"astronomical":"0.2892655363","lunar":"112.5242936207","kilometers":"43273508.094887681","miles":"26888911.0747887578"},"orbiting_body":"Earth"},{"close_approach_date":"2058-03-31","close_approach_date_full":"2058-Mar-31 23:40","epoch_date_close_approach":2784843600000,"relative_velocity":{"kilometers_per_second":"12.1371562331","kilometers_per_hour":"43693.7624391524","miles_per_hour":"27149.6047778167"},"miss_distance":{"astronomical":"0.2136866427","lunar":"83.1241040103","kilometers":"31967066.595371049","miles":"19863414.1036125162"},"orbiting_body":"Earth"},{"close_approach_date":"2058-12-20","close_approach_date_full":"2058-Dec-20 16:38","epoch_date_close_approach":2807627880000,"relative_velocity":{"kilometers_per_second":"6.8588561427","kilometers_per_hour":"24691.882113799","miles_per_hour":"15342.5753056547"},"miss_distance":{"astronomical":"0.3301692248","lunar":"128.4358284472","kilometers":"49392612.769631176","miles":"30691146.4076791888"},"orbiting_body":"Earth"},{"close_approach_date":"2059-09-28","close_approach_date_full":"2059-Sep-28 22:36","epoch_date_close_approach":2832014160000,"relative_velocity":{"kilometers_per_second":"15.4693572096","kilometers_per_hour":"55689.6859543937","miles_per_hour":"34603.4051420509"},"miss_distance":{"astronomical":"0.3280300534","lunar":"127.6036907726","kilometers":"49072597.284626258","miles":"30492298.0060200404"},"orbiting_body":"Earth"},{"close_approach_date":"2065-03-20","close_approach_date_full":"2065-Mar-20 00:05","epoch_date_close_approach":3004733100000,"relative_velocity":{"kilometers_per_second":"19.9097792492","kilometers_per_hour":"71675.2052971362","miles_per_hour":"44536.1852025455"},"miss_distance":{"astronomical":"0.4740587288","lunar":"184.4088455032","kilometers":"70918176.083387656","miles":"44066511.2269394128"},"orbiting_body":"Earth"},{"close_approach_date":"2066-01-18","close_approach_date_full":"2066-Jan-18 14:48","epoch_date_close_approach":3031051680000,"relative_velocity":{"kilometers_per_second":"6.5322221342","kilometers_per_hour":"23515.9996832373","miles_per_hour":"14611.9276920649"},"miss_distance":{"astronomical":"0.3153169152","lunar":"122.6582800128","kilometers":"47170738.888890624","miles":"29310537.9978496512"},"orbiting_body":"Earth"},{"close_approach_date":"2066-09-16","close_approach_date_full":"2066-Sep-16 02:10","epoch_date_close_approach":3051828600000,"relative_velocity":{"kilometers_per_second":"7.7630498489","kilometers_per_hour":"27946.9794561575","miles_per_hour":"17365.1662070774"},"miss_distance":{"astronomical":"0.0695616719","lunar":"27.0594903691","kilometers":"10406277.949878853","miles":"6466161.2781720514"},"orbiting_body":"Earth"},{"close_approach_date":"2073-02-15","close_approach_date_full":"2073-Feb-15 06:23","epoch_date_close_approach":3254365380000,"relative_velocity":{"kilometers_per_second":"5.2793618377","kilometers_per_hour":"19005.702615676","miles_per_hour":"11809.4044947238"},"miss_distance":{"astronomical":"0.2582809573","lunar":"100.4712923897","kilometers":"38638281.073640951","miles":"24008714.5602730838"},"orbiting_body":"Earth"},{"close_approach_date":"2073-07-27","close_approach_date_full":"2073-Jul-27 19:01","epoch_date_close_approach":3268407660000,"relative_velocity":{"kilometers_per_second":"4.5660712634","kilometers_per_hour":"16437.8565482037","miles_per_hour":"10213.8448090769"},"miss_distance":{"astronomical":"0.1122557661","lunar":"43.6674930129","kilometers":"16793223.503778207","miles":"10434825.2159729766"},"orbiting_body":"Earth"},{"close_approach_date":"2080-05-09","close_approach_date_full":"2080-May-09 08:16","epoch_date_close_approach":3482468160000,"relative_velocity":{"kilometers_per_second":"4.1981252332","kilometers_per_hour":"15113.2508396506","miles_per_hour":"9390.7863342262"},"miss_distance":{"astronomical":"0.0869322711","lunar":"33.8166534579","kilometers":"13004882.590822557","miles":"8080859.3275110066"},"orbiting_body":"Earth"},{"close_approach_date":"2080-11-12","close_approach_date_full":"2080-Nov-12 11:05","epoch_date_close_approach":3498635100000,"relative_velocity":{"kilometers_per_second":"5.6750601206","kilometers_per_hour":"20430.2164343206","miles_per_hour":"12694.5419838699"},"miss_distance":{"astronomical":"0.2722040595","lunar":"105.8873791455","kilometers":"40721147.506553265","miles":"25302947.747294457"},"orbiting_body":"Earth"},{"close_approach_date":"2087-04-07","close_approach_date_full":"2087-Apr-07 12:15","epoch_date_close_approach":3700556100000,"relative_velocity":{"kilometers_per_second":"8.6588863809","kilometers_per_hour":"31171.990971198","miles_per_hour":"19369.0629454091"},"miss_distance":{"astronomical":"0.0958110564","lunar":"37.2705009396","kilometers":"14333129.959889868","miles":"8906193.9713734584"},"orbiting_body":"Earth"},{"close_approach_date":"2087-12-07","close_approach_date_full":"2087-Dec-07 18:34","epoch_date_close_approach":3721660440000,"relative_velocity":{"kilometers_per_second":"6.6198956457","kilometers_per_hour":"23831.6243245628","miles_per_hour":"14808.0445698929"},"miss_distance":{"astronomical":"0.3190451801","lunar":"124.1085750589","kilometers":"47728479.376726387","miles":"29657101.8666106606"},"orbiting_body":"Earth"},{"close_approach_date":"2088-10-03","close_approach_date_full":"2088-Oct-03 16:07","epoch_date_close_approach":3747658020000,"relative_velocity":{"kilometers_per_second":"19.0971395294","kilometers_per_hour":"68749.7023058922","miles_per_hour":"42718.3914133473"},"miss_distance":{"astronomical":"0.4496613226","lunar":"174.9182544914","kilometers":"67268376.082342862","miles":"41798630.6693090156"},"orbiting_body":"Earth"},{"close_approach_date":"2094-03-26","close_approach_date_full":"2094-Mar-26 23:46","epoch_date_close_approach":3920485560000,"relative_velocity":{"kilometers_per_second":"15.5540132096","kilometers_per_hour":"55994.4475546255","miles_per_hour":"34792.772148595"},"miss_distance":{"astronomical":"0.3286062194","lunar":"127.8278193466","kilometers":"49158790.490992678","miles":"30545855.9809398364"},"orbiting_body":"Earth"},{"close_approach_date":"2095-01-02","close_approach_date_full":"2095-Jan-02 03:15","epoch_date_close_approach":3944776500000,"relative_velocity":{"kilometers_per_second":"6.8718895542","kilometers_per_hour":"24738.8023950601","miles_per_hour":"15371.7297437528"},"miss_distance":{"astronomical":"0.3314539044","lunar":"128.9355688116","kilometers":"49584798.101423628","miles":"30810564.8353489464"},"orbiting_body":"Earth"},{"close_approach_date":"2095-09-24","close_approach_date_full":"2095-Sep-24 03:08","epoch_date_close_approach":3967672080000,"relative_velocity":{"kilometers_per_second":"12.2653232085","kilometers_per_hour":"44155.1635507735","miles_per_hour":"27436.3014852015"},"miss_distance":{"astronomical":"0.2199995266","lunar":"85.5798158474","kilometers":"32911460.580368342","miles":"20450233.3147214396"},"orbiting_body":"Earth"},{"close_approach_date":"2102-01-28","close_approach_date_full":"2102-Jan-28 19:55","epoch_date_close_approach":4167921300000,"relative_velocity":{"kilometers_per_second":"6.2129455211","kilometers_per_hour":"22366.6038761256","miles_per_hour":"13897.7378362515"},"miss_distance":{"astronomical":"0.300728934","lunar":"116.983555326","kilometers":"44988407.97377058","miles":"27954500.447490804"},"orbiting_body":"Earth"},{"close_approach_date":"2102-09-11","close_approach_date_full":"2102-Sep-11 20:32","epoch_date_close_approach":4187449920000,"relative_velocity":{"kilometers_per_second":"5.6620412591","kilometers_per_hour":"20383.3485326802","miles_per_hour":"12665.4200924312"},"miss_distance":{"astronomical":"0.0234318986","lunar":"9.1150085554","kilometers":"3505362.120615982","miles":"2178131.0204732716"},"orbiting_body":"Earth"},{"close_approach_date":"2116-04-12","close_approach_date_full":"2116-Apr-12 17:50","epoch_date_close_approach":4616157000000,"relative_velocity":{"kilometers_per_second":"6.443969174","kilometers_per_hour":"23198.2890265168","miles_per_hour":"14414.5146453932"},"miss_distance":{"astronomical":"0.0194621971","lunar":"7.5707946719","kilometers":"2911503.231680177","miles":"1809124.2179613626"},"orbiting_body":"Earth"},{"close_approach_date":"2116-12-02","close_approach_date_full":"2116-Dec-02 05:11","epoch_date_close_approach":4636329060000,"relative_velocity":{"kilometers_per_second":"6.3739829531","kilometers_per_hour":"22946.3386311129","miles_per_hour":"14257.9624677601"},"miss_distance":{"astronomical":"0.3033826128","lunar":"118.0158363792","kilometers":"45385392.669914736","miles":"28201175.2992099168"},"orbiting_body":"Earth"},{"close_approach_date":"2117-10-07","close_approach_date_full":"2117-Oct-07 16:24","epoch_date_close_approach":4663067040000,"relative_velocity":{"kilometers_per_second":"19.9725505844","kilometers_per_hour":"71901.1821039084","miles_per_hour":"44676.5984022869"},"miss_distance":{"astronomical":"0.4782864659","lunar":"186.0534352351","kilometers":"71550636.548467633","miles":"44459503.9366260154"},"orbiting_body":"Earth"}],"orbital_data":{"orbit_id":"216","orbit_determination_date":"2021-06-29 11:09:44","first_observation_date":"2004-03-15","last_observation_date":"2021-05-12","data_arc_in_days":6267,"observations_used":7300,"orbit_uncertainty":"0","minimum_orbit_intersection":".000244177","jupiter_tisserand_invariant":"6.465","epoch_osculation":"2459600.5","eccentricity":".1913839820496301","semi_major_axis":".9226958893585622","inclination":"3.338951721314444","ascending_node_longitude":"203.9614276677966","orbital_period":"323.7325033699091","perihelion_distance":".7461066758322956","perihelion_argument":"126.5959309724569","aphelion_distance":"1.099285102884829","perihelion_time":"2459748.288907609831","mean_anomaly":"195.6544178118983","mean_motion":"1.112029210081047","equinox":"J2000","orbit_class":{"orbit_class_type":"ATE","orbit_class_description":"Near-Earth asteroid orbits similar to that of 2062 Aten","orbit_class_range":"a (semi-major axis) < 1.0 AU; q (perihelion) > 0.983 AU"}},"is_sentry_object":false} \ No newline at end of file diff --git a/4. Углубленный Python/3. Отладка и тестирование/asteroid.py b/4. Углубленный Python/3. Отладка и тестирование/asteroid.py new file mode 100644 index 0000000..875d514 --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/asteroid.py @@ -0,0 +1,39 @@ +import requests + + +class Asteroid: + BASE_API_URL = "https://api.nasa.gov/neo/rest/v1/neo/{}?api_key=DEMO_KEY" + + def __init__(self, spk_id): + self.api_url = self.BASE_API_URL.format(spk_id) + + def get_data(self): + return requests.get(self.api_url).json() + + @property + def name(self): + return self.get_data()["name"] + + @property + def diameter(self): + return int( + self.get_data()["estimated_diameter"]["meters"][ + "estimated_diameter_max" + ] + ) + + @property + def closest_approach(self): + closest = {"date": None, "distance": float("inf")} + + for approach in self.get_data()["close_approach_data"]: + distance = float(approach["miss_distance"]["lunar"]) + if distance < closest["distance"]: + closest.update( + { + "date": approach["close_approach_date"], + "distance": distance, + } + ) + + return closest diff --git a/4. Углубленный Python/3. Отладка и тестирование/test_asteroid.py b/4. Углубленный Python/3. Отладка и тестирование/test_asteroid.py new file mode 100644 index 0000000..2e92d83 --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/test_asteroid.py @@ -0,0 +1,23 @@ +import json +import unittest +from unittest.mock import patch + +from asteroid import Asteroid + +class TestAsteroid(unittest.TestCase): + def setUp(self): + self.asteroid = Asteroid(2099942) + + def mocked_get_data(self): + with open("apophis_fixture.txt") as f: + return json.loads(f.read()) + + @patch("asteroid.Asteroid.get_data", mocked_get_data) + def test_name(self): + self.assertEqual( + self.asteroid.name, "99942 Apophis (2004 MN4)" + ) + + @patch("asteroid.Asteroid.get_data", mocked_get_data) + def test_diameter(self): + self.assertEqual(self.asteroid.diameter, 682) \ No newline at end of file diff --git a/4. Углубленный Python/3. Отладка и тестирование/test_division.py b/4. Углубленный Python/3. Отладка и тестирование/test_division.py new file mode 100644 index 0000000..459bb7d --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/test_division.py @@ -0,0 +1,5 @@ +import unittest + +class TestDivision(unittest.TestCase): + def test_integer_division(self): + self.assertIs(10 / 5, 2) \ No newline at end of file diff --git a/4. Углубленный Python/3. Отладка и тестирование/test_python.py b/4. Углубленный Python/3. Отладка и тестирование/test_python.py new file mode 100644 index 0000000..d2e471c --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/test_python.py @@ -0,0 +1,11 @@ +import unittest + +class TestPython(unittest.TestCase): + def test_float_to_int_coercion(self): + self.assertEqual(1, int(1.0)) + + def test_get_empty_dict(self): + self.assertIsNone({}.get("key")) + + def test_trueness(self): + self.assertTrue(bool(10)) \ No newline at end of file diff --git a/4. Углубленный Python/3. Отладка и тестирование/wc_web.py b/4. Углубленный Python/3. Отладка и тестирование/wc_web.py new file mode 100644 index 0000000..60036d9 --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/wc_web.py @@ -0,0 +1,18 @@ +import re +import requests + +def main(site_url, substring): + site_code = get_site_code(site_url) + matching_substrings = get_matching_substrings(site_code, substring) + print(f"'{substring}' found {len(matching_substrings)} times in {site_url}") + +def get_site_code(site_url): + if not site_url.startswith("http"): + site_url = "http://" + site_url + + return requests.get(site_url).text + +def get_matching_substrings(source, substring): + return re.findall(substring, source) + +main("vniitf.ru", "python") \ No newline at end of file diff --git a/4. Углубленный Python/3. Отладка и тестирование/Документация.ipynb b/4. Углубленный Python/3. Отладка и тестирование/Документация.ipynb new file mode 100644 index 0000000..d64aea5 --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/Документация.ipynb @@ -0,0 +1,44 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5f13d398", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "30ab5463", + "metadata": {}, + "source": [ + "- [pdb](https://docs.python.org/3/library/pdb.html \"27.3. pdb — The Python Debugger\")\n", + "- [unittest](https://docs.python.org/3/library/unittest.html \"26.4. unittest — Unit testing framework\")\n", + "- [unittest.mock](https://docs.python.org/3/library/unittest.mock.html \"26.5. unittest.mock — mock object library\")\n", + "- [unittest.mock examples](https://docs.python.org/3/library/unittest.mock-examples.html \"26.6. unittest.mock — getting started\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/3. Отладка и тестирование/Отладка.ipynb b/4. Углубленный Python/3. Отладка и тестирование/Отладка.ipynb new file mode 100644 index 0000000..71928f9 --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/Отладка.ipynb @@ -0,0 +1,615 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a85683d0", + "metadata": {}, + "source": [ + "# Отладка #" + ] + }, + { + "cell_type": "markdown", + "id": "696f9c7a", + "metadata": {}, + "source": [ + "Привет. На этой лекции мы с вами поговорим про отладку в Python. Скорее всего отладкой вы уже занимались, когда пытались выяснить, почему ваша программа не работает или работает некорректно. Если вы программируете в IDE, скорее всего, там уже есть инструментарий для отладки вашего кода, и вы можете запускать вашу программу под отладчиком, ставить какие-то брейкпоинты, следить за переменными, и так далее. Мы с вами разберём классический механизм отладки с помощью Python Debugger'а и делать мы это будем на примере." + ] + }, + { + "cell_type": "markdown", + "id": "7a9e869b", + "metadata": {}, + "source": [ + "Мы напишем программу, которая принимает на вход сайт, URL сайта и какую-то строчку, и ищет в коде сайта эту строчку и считает, сколько раз там встретилась эта строка. Давайте напишем нашу программу и попробуем её отладить." + ] + }, + { + "cell_type": "markdown", + "id": "ec880120", + "metadata": {}, + "source": [ + "Определим функцию `main`, которая принимает `site_url` и `substring`, и давайте получим вначале `site_code` с помощью функции `get_site_code`, которой передадим `site_url`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0f852142", + "metadata": {}, + "outputs": [], + "source": [ + "def main(site_url, substring):\n", + " site_code = get_site_code(site_url)" + ] + }, + { + "cell_type": "markdown", + "id": "463acf3b", + "metadata": {}, + "source": [ + "Давайте определим ниже функцию `get_site_code`, которая принимает `site_url`, и чтобы получить `site_code` мы можем воспользоваться уже знакомой вам библиотекой `requests` и передать туда `site_url` и вернуть текст.\n", + "\n", + "Давайте ещё проверим, что нам, допустим, передан корректный `site_url`. Здесь у нас с этого начинается, со схемы, мы эту схему добавим. Простая проверка, почему бы и нет?" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "edb2cff5", + "metadata": {}, + "outputs": [], + "source": [ + "def get_site_code(site_url):\n", + " if not site_url.startswith(\"http\"):\n", + " site_url = \"http://\" + site_url\n", + " \n", + " return requests.get(site_url).text" + ] + }, + { + "cell_type": "markdown", + "id": "27c8dac9", + "metadata": {}, + "source": [ + "Итак, у нас есть наш `site_code`, осталось импортировать `requests`, чтоб всё работало, теперь нам нужно получить все подстроки, которые мы нашли." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a488e462", + "metadata": {}, + "outputs": [], + "source": [ + "import requests" + ] + }, + { + "cell_type": "markdown", + "id": "8b3e4b8c", + "metadata": {}, + "source": [ + "Давайте назовем переменнную `matching_substrings` и вызовем функцию `get_matching_substrings`, которой передадим `site_code` и `substring`. Отлично." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "257bb19e", + "metadata": {}, + "outputs": [], + "source": [ + "def main(site_url, substring):\n", + " site_code = get_site_code(site_url)\n", + " matching_substrings = get_matching_substrings(site_code, substring)" + ] + }, + { + "cell_type": "markdown", + "id": "34e4025a", + "metadata": {}, + "source": [ + "Определим чуть ниже эту самую функцию `get_matching_substrings`. Давайте, пусть у нас переменные будут называться `source` и `substring`, потому что мы можем использовать эту функцию в разных местах, не только здесь. Для того чтобы найти вхождения подстроки в строку мы воспользуемся регулярными выражениями стандартной библиотеки, модулем `re`, используем функцию `findall` и передадим в неё `source` и `substring`. Таким образом, мы будeм искать не только подстроки, но можем искать, например, по какому-то регулярному выражению." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "89950f89", + "metadata": {}, + "outputs": [], + "source": [ + "def get_matching_substrings(source, substring):\n", + " return re.findall(source, substring)" + ] + }, + { + "cell_type": "markdown", + "id": "9d62a32a", + "metadata": {}, + "source": [ + "Давайте импортируем `re` для того, чтобы всё работало. Отлично." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "7bc159b2", + "metadata": {}, + "outputs": [], + "source": [ + "import re" + ] + }, + { + "cell_type": "markdown", + "id": "302a5e85", + "metadata": {}, + "source": [ + "Мы получили подстроки и нам нужно их посчитать. Давайте выведем сразу наш результат и мы скажем, что мы нашли подстроку сколько-то раз в таком-то сайте. Давайте отформатируем. Форматируем. Напишем `substring`, найдена она ровно `len(matching_substring)` раз `site_url`. Отлично!" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c01187fc", + "metadata": {}, + "outputs": [], + "source": [ + "def main(site_url, substring):\n", + " site_code = get_site_code(site_url)\n", + " matching_substrings = get_matching_substrings(site_code, substring)\n", + " print(f\"'{substring}' found {len(matching_substrings)} times in {site_url}\")" + ] + }, + { + "cell_type": "markdown", + "id": "dc340754", + "metadata": {}, + "source": [ + "Это и будет результатом. И давайте вызовем нашу функцию. В этот момент уже начнётся процесс отладки, потому что скорее всего сразу она не заработает. И, например, не заработает она потому, что мы не передали в функцию main параметра." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "277aa27d", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "main() missing 2 required positional arguments: 'site_url' and 'substring'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m: main() missing 2 required positional arguments: 'site_url' and 'substring'" + ] + } + ], + "source": [ + "main()" + ] + }, + { + "cell_type": "markdown", + "id": "ce93873e", + "metadata": {}, + "source": [ + "Итак, процесс отладки уже начат, мы смотрим на исключения, читаем, что нам исключение это говорит. Давайте исправим это, передадим туда сайт vniitf.ru и python, допустим, поскольку python будем искать в сайте vniitf.ru. Давайте запустим и посмотрим, что у нас не работает дальше. Итак, у нас какая-то проблема в `get_matching_substrings` очевидно." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3a63069d", + "metadata": {}, + "outputs": [ + { + "ename": "error", + "evalue": "bad character range 0-% at position 20016 (line 412, column 353)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31merror\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"sinfo\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"python\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mmain\u001b[0;34m(site_url, substring)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36mget_matching_substrings\u001b[0;34m(source, substring)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/usr/lib64/python3.6/re.py\u001b[0m in \u001b[0;36mfindall\u001b[0;34m(pattern, string, flags)\u001b[0m\n\u001b[1;32m 220\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 221\u001b[0m Empty matches are included in the result.\"\"\"\n\u001b[0;32m--> 222\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_compile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 223\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 224\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfinditer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/usr/lib64/python3.6/re.py\u001b[0m in \u001b[0;36m_compile\u001b[0;34m(pattern, flags)\u001b[0m\n\u001b[1;32m 299\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0msre_compile\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misstring\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 300\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"first argument must be string or compiled pattern\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 301\u001b[0;31m \u001b[0mp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msre_compile\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcompile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 302\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mflags\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0mDEBUG\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 303\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_cache\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m>=\u001b[0m \u001b[0m_MAXCACHE\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/usr/lib64/python3.6/sre_compile.py\u001b[0m in \u001b[0;36mcompile\u001b[0;34m(p, flags)\u001b[0m\n\u001b[1;32m 560\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misstring\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mp\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 561\u001b[0m \u001b[0mpattern\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 562\u001b[0;31m \u001b[0mp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msre_parse\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mparse\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 563\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 564\u001b[0m \u001b[0mpattern\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/usr/lib64/python3.6/sre_parse.py\u001b[0m in \u001b[0;36mparse\u001b[0;34m(str, flags, pattern)\u001b[0m\n\u001b[1;32m 853\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 854\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 855\u001b[0;31m \u001b[0mp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_parse_sub\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0mSRE_FLAG_VERBOSE\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 856\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mVerbose\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 857\u001b[0m \u001b[0;31m# the VERBOSE flag was switched on inside the pattern. to be\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/usr/lib64/python3.6/sre_parse.py\u001b[0m in \u001b[0;36m_parse_sub\u001b[0;34m(source, state, verbose, nested)\u001b[0m\n\u001b[1;32m 414\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 415\u001b[0m itemsappend(_parse(source, state, verbose, nested + 1,\n\u001b[0;32m--> 416\u001b[0;31m not nested and not items))\n\u001b[0m\u001b[1;32m 417\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0msourcematch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"|\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 418\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/usr/lib64/python3.6/sre_parse.py\u001b[0m in \u001b[0;36m_parse\u001b[0;34m(source, state, verbose, nested, first)\u001b[0m\n\u001b[1;32m 551\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhi\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0mlo\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 552\u001b[0m \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"bad character range %s-%s\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mthis\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mthat\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 553\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0msource\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0merror\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mthis\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mthat\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 554\u001b[0m \u001b[0msetappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mRANGE\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 555\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31merror\u001b[0m: bad character range 0-% at position 20016 (line 412, column 353)" + ] + } + ], + "source": [ + "main(\"vniitf.ru\", \"python\")" + ] + }, + { + "cell_type": "markdown", + "id": "2457b754", + "metadata": {}, + "source": [ + "Совершенно непонятно, что же случилось. По этой ошибке нельзя однозначно сказать, почему наша программа не работает. В данном случае на помощь нам может прийти отладчик. Опять же простейшая отладка заключается в том, что мы смотрим на исключения или на ошибку и, допустим, выводим какие-то переменные, смотрим, что они корректные или некорректные. Можно использовать отладчик. Для того чтобы запустить отладчик в нашей программе, мы можем импортировать `pdb` и сказать, что, пожалуйста, запусти отладку в этом месте. `pdb.set_trace` — это стандартный способ, импорт прямо здесь, `set_trace` прямо здесь, и давайте запустим нашу программу под отладчиком." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "21d4d55d", + "metadata": {}, + "outputs": [], + "source": [ + "def main(site_url, substring):\n", + " import pdb\n", + " pdb.set_trace()\n", + " \n", + " site_code = get_site_code(site_url)\n", + " matching_substrings = get_matching_substrings(site_code, substring)\n", + " print(f\"'{substring}' found {len(matching_substrings)} times in {site_url}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "894621f2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> \u001b[0;32m\u001b[0m(5)\u001b[0;36mmain\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 3 \u001b[0;31m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 6 \u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 7 \u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> ll\n", + "\u001b[1;32m 1 \u001b[0m\u001b[0;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 2 \u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 3 \u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 4 \u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 6 \u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 7 \u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\n", + "ipdb> help\n", + "\n", + "Documented commands (type help ):\n", + "========================================\n", + "EOF cl disable interact next psource rv undisplay\n", + "a clear display j p q s unt \n", + "alias commands down jump pdef quit skip_hidden until \n", + "args condition enable l pdoc r source up \n", + "b cont exit list pfile restart step w \n", + "break continue h ll pinfo return tbreak whatis \n", + "bt d help longlist pinfo2 retval u where \n", + "c debug ignore n pp run unalias \n", + "\n", + "Miscellaneous help topics:\n", + "==========================\n", + "exec pdb\n", + "\n", + "ipdb> ? p\n", + "p expression\n", + " Print the value of the expression.\n", + "ipdb> args\n", + "site_url = 'sinfo'\n", + "substring = 'python'\n", + "ipdb> ll\n", + "\u001b[1;32m 1 \u001b[0m\u001b[0;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 2 \u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 3 \u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 4 \u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 6 \u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 7 \u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\n", + "ipdb> next\n", + "> \u001b[0;32m\u001b[0m(6)\u001b[0;36mmain\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 3 \u001b[0;31m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m----> 6 \u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 7 \u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> step\n", + "--Call--\n", + "> \u001b[0;32m\u001b[0m(1)\u001b[0;36mget_matching_substrings\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m----> 1 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 2 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> n\n", + "> \u001b[0;32m\u001b[0m(2)\u001b[0;36mget_matching_substrings\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 1 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m----> 2 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> s\n", + "--Call--\n", + "> \u001b[0;32m/usr/lib64/python3.6/re.py\u001b[0m(214)\u001b[0;36mfindall\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 212 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_compile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmaxsplit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 213 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m--> 214 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 215 \u001b[0;31m \"\"\"Return a list of all non-overlapping matches in the string.\n", + "\u001b[0m\u001b[0;32m 216 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> q\n" + ] + }, + { + "ename": "BdbQuit", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mBdbQuit\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"sinfo\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"python\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mmain\u001b[0;34m(site_url, substring)\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36mget_matching_substrings\u001b[0;34m(source, substring)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/usr/lib64/python3.6/re.py\u001b[0m in \u001b[0;36mfindall\u001b[0;34m(pattern, string, flags)\u001b[0m\n\u001b[1;32m 212\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0m_compile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmaxsplit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 213\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 214\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 215\u001b[0m \"\"\"Return a list of all non-overlapping matches in the string.\n\u001b[1;32m 216\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/usr/lib64/python3.6/bdb.py\u001b[0m in \u001b[0;36mtrace_dispatch\u001b[0;34m(self, frame, event, arg)\u001b[0m\n\u001b[1;32m 51\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_line\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 52\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'call'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 53\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 54\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'return'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_return\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/usr/lib64/python3.6/bdb.py\u001b[0m in \u001b[0;36mdispatch_call\u001b[0;34m(self, frame, arg)\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrace_dispatch\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 85\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0muser_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 86\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mquitting\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mBdbQuit\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 87\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrace_dispatch\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 88\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mBdbQuit\u001b[0m: " + ] + } + ], + "source": [ + "main(\"vniitf.ru\", \"python\")" + ] + }, + { + "cell_type": "markdown", + "id": "997970f2", + "metadata": {}, + "source": [ + "Итак, мы запустили программу, однако, она не дошла до ошибки, она остановилась именно в том месте, где мы наш отладчик определили. Мы можем посмотреть, где это находится, с помощью команды `long list`. Итак, мы находимся ровно за тем местом, где наш отладчик определён и смотрим на функцию `get_site_code`.\n", + "\n", + "У отладчика довольно много команд, их можно посмотреть с помощью функции `help`. Функция `help` выводит все команды, мы можем также написать знак вопроса вместо функции `help` и, например, мы можем посмотреть про какую-то конкретную команду, что она делает.\n", + "\n", + "Например, давайте посмотрим, что делает команда `p`. Команда `p` — это сокращение от `print`'а и она выводит просто выражение. Мы можем вывести какую-то переменную, которая находится в области нашей видимости. Есть полезная команда `args`, которая говорит о том, какие аргументы переданы функции, в которой мы находимся. Сейчас мы находимся в функции `main` и аргументы переданы правильно и всё корректно. Итак, дальше у нас есть несколько опций. Мы остановились в отладчике и мы можем продолжать как-то наше исполнение кода под отладчиком. Мы можем написать `continue` или `c` и продолжить исполнение дальше до конца, пока у нас не закончится программа или не упадёт исключение или не появится какой-то брейкпоинт.\n", + "\n", + "Однако, нам интересно отлаживать, поэтому это не вариант. Дальше, у нас есть несколько опций. Мы можем написать `step` и провалиться внутрь функции, однако у нас ошибка не внутри функции, а дальше, поэтому мы напишем `next` и перейдём на следующую строку. Следующая строка — это вызов функции `get_matching_substrings`, куда нам и нужно. Чтобы пройти внутрь функции, не пропуская её, мы можем написать `step` и `less` и оказаться внутри функции внутри функции `get_matching_substrings`. Итак, давайте пойдём дальше, и дальше у нас вызов функции `findall`. Именно там и была ошибка. Давайте посмотрим, что же у нас происходит. Итак, мы в `findall` передаём `source` и `substring` и пытаемся найти `substring` в `source`. Давайте зайдём в `findall`, которая определена в стандартной библиотеке и посмотрим, что же там происходит. Итак, на самом деле, внимательный читатель уже заметит, в чём была ошибка, если мы посмотрим на определение `findall`. `findall` принимает вначале паттерн, а потом строку, и ищет этот паттерн в строке, а мы передаём ровно наоборот. Именно это и является ошибкой. Давайте выйдем или продолжим исполнение. Давайте выйдем. Чтобы выйти, нужно написать в `query quit`, и мы просто выйдем из `debugger`'а по исключению.\n", + "\n", + "Итак, давайте поменяем местами и посмотрим, неужели это и была ошибка наша." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "b6bbf6c6", + "metadata": {}, + "outputs": [], + "source": [ + "def get_matching_substrings(source, substring):\n", + " return re.findall(substring, source)" + ] + }, + { + "cell_type": "markdown", + "id": "c3d3f48a", + "metadata": {}, + "source": [ + "Давайте запустим ещё с отладчиком, и я покажу вам ещё один момент, который бывает полезным. Часто вам необходимо отлаживать не в одном месте, а вы хотите, например, продолжить исполнение до какого-то определённого момента. На помощь нам могут прийти брейкпоинты, которые, например, в IDE ставятся очень удобно." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "a5280d4a", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> \u001b[0;32m\u001b[0m(5)\u001b[0;36mmain\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 3 \u001b[0;31m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 6 \u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 7 \u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> ll\n", + "\u001b[1;32m 1 \u001b[0m\u001b[0;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 2 \u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 3 \u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 4 \u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 6 \u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[1;32m 7 \u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\n", + "ipdb> b\n", + "ipdb> b 7\n", + "Breakpoint 1 at :7\n", + "ipdb> cont\n", + "> \u001b[0;32m\u001b[0m(7)\u001b[0;36mmain\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 3 \u001b[0;31m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 6 \u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;31m1\u001b[0;32m---> 7 \u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> q\n" + ] + }, + { + "ename": "BdbQuit", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mBdbQuit\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"sinfo\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"python\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mmain\u001b[0;34m(site_url, substring)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mmain\u001b[0;34m(site_url, substring)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/usr/lib64/python3.6/bdb.py\u001b[0m in \u001b[0;36mtrace_dispatch\u001b[0;34m(self, frame, event, arg)\u001b[0m\n\u001b[1;32m 49\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;31m# None\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 50\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'line'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 51\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_line\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 52\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'call'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/usr/lib64/python3.6/bdb.py\u001b[0m in \u001b[0;36mdispatch_line\u001b[0;34m(self, frame)\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstop_here\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbreak_here\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0muser_line\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 70\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mquitting\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mBdbQuit\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 71\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrace_dispatch\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 72\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mBdbQuit\u001b[0m: " + ] + } + ], + "source": [ + "main(\"vniitf.ru\", \"python\")" + ] + }, + { + "cell_type": "markdown", + "id": "9707481e", + "metadata": {}, + "source": [ + "Здесь можно воспользоваться командой `b`, которая, если просто напишем `b`, она выводит все брейкопоинты, которые мы уже определили. Если мы напишем `b` с номером строки, например, `b 7`, мы поставим брейкопоинт на десятую строку. И когда исполнение интерпретатором программы дойдёт до этой строки, он остановится. Мы можем написать `continue` и остановиться ровно на нашем брейкпоинте. Давайте выйдем, уберём `debugger` и проверим, корректно ли работает наша программа." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "320d9e39", + "metadata": {}, + "outputs": [], + "source": [ + "def main(site_url, substring): \n", + " site_code = get_site_code(site_url)\n", + " matching_substrings = get_matching_substrings(site_code, substring)\n", + " print(f\"'{substring}' found {len(matching_substrings)} times in {site_url}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f79af11d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "'python' found 19 times in sinfo\n" + ] + } + ], + "source": [ + "main(\"vniitf.ru\", \"python\")" + ] + }, + { + "cell_type": "markdown", + "id": "0dacbb93", + "metadata": {}, + "source": [ + "Мы нашли 19 раза в `site_code` vniitf.ru слово python." + ] + }, + { + "cell_type": "markdown", + "id": "3a274579", + "metadata": {}, + "source": [ + "Отлично, мы отладили нашу программу и посмотрели, почему она не работает с помощью `debugger`'а. Точно так же из `debugger`'а можно работать не в Jupyter ноутбуке, а в консоли, и можно запускать программу под отладкой с помощью такого механизма." + ] + }, + { + "cell_type": "markdown", + "id": "2051dc05", + "metadata": {}, + "source": [ + "Код нашей программы в данном случае называется `wc_web`, и мы можем запустить его под отладчиком. Здесь происходит всё то же самое, мы можем выполнять все те же самые команды и ставить брейкпоинты на какие-то места. Например, мы можем поставить брейкпоинт на шестую строку, продолжить исполнение, дойти до шестой строки, посмотреть аргументы, и так далее." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "5550ac58", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> /home/mikhaylovaf/projects/python/4. Углубленный Python/3. Отладка и тестирование/wc_web.py(1)()\n", + "-> import re\n", + "(Pdb) \n", + "--KeyboardInterrupt--\n", + "(Pdb) " + ] + } + ], + "source": [ + "! python -m pdb wc_web.py" + ] + }, + { + "cell_type": "markdown", + "id": "a2d4ee94", + "metadata": {}, + "source": [ + "Итак, мы с вами разобрали отладку в Python'е и теперь можем отлаживать наши программы и выяснять, почему они не работают. Удачи." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "7ee7851b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "'python' found 19 times in sinfo\n" + ] + } + ], + "source": [ + "import re\n", + "import requests\n", + "\n", + "def main(site_url, substring):\n", + " site_code = get_site_code(site_url)\n", + " matching_substrings = get_matching_substrings(site_code, substring)\n", + " print(f\"'{substring}' found {len(matching_substrings)} times in {site_url}\")\n", + " \n", + "def get_site_code(site_url):\n", + " if not site_url.startswith(\"http\"):\n", + " site_url = \"http://\" + site_url\n", + " \n", + " return requests.get(site_url).text\n", + "\n", + "def get_matching_substrings(source, substring):\n", + " return re.findall(substring, source)\n", + "\n", + "main(\"vniitf.ru\", \"python\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/3. Отладка и тестирование/Тест по блоку (Clear).ipynb b/4. Углубленный Python/3. Отладка и тестирование/Тест по блоку (Clear).ipynb new file mode 100644 index 0000000..f3d52f8 --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/Тест по блоку (Clear).ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "400513aa", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "5f26f612", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "de522557", + "metadata": {}, + "source": [ + "##### 1. Для чего нужны контекстные менеджеры?\n", + "\n", + " - [ ] Они управляют переключением контекста между модулями\n", + " - [ ] Они используются для определения логики в начале и конце блока кода\n", + " - [ ] Они управляют переключением контекста между функциями" + ] + }, + { + "cell_type": "markdown", + "id": "ca2f711d", + "metadata": {}, + "source": [ + "##### 2. Что такое дескриптор?\n", + "\n", + " - [ ] Описание класса\n", + " - [ ] Объект с методами `__get__`/`__set__`/`__delete__`\n", + " - [ ] Метод доступа к атрибутам класса\n", + " - [ ] Функция с `yield`" + ] + }, + { + "cell_type": "markdown", + "id": "e8d93626", + "metadata": {}, + "source": [ + "##### 3. Как закончить исполнение итератора?\n", + "\n", + " - [ ] Вернуть `None`\n", + " - [ ] Вызвать метод `__exit__`\n", + " - [ ] Выбросить исключение `StopIteration`" + ] + }, + { + "cell_type": "markdown", + "id": "de9f3ce1", + "metadata": {}, + "source": [ + "##### 4. Для чего нужны метаклассы?\n", + "\n", + " - [ ] Для управление процессом создания классов\n", + " - [ ] Для создания дескрипторов\n", + " - [ ] Для создания метаобъектов" + ] + }, + { + "cell_type": "markdown", + "id": "b5e7333f", + "metadata": {}, + "source": [ + "##### 5. С помощью какого оператора можно получить значение из `__enter__`?\n", + "\n", + " - [ ] `to`\n", + " - [ ] `from`\n", + " - [ ] `as`" + ] + }, + { + "cell_type": "markdown", + "id": "be3f88b9", + "metadata": {}, + "source": [ + "##### 6. Метод `setUp` у наследующегося от `TestCase` класса\n", + "\n", + " - [ ] вызывается перед запуском тестового класса\n", + " - [ ] нужен для подготовки данных перед запуском тестов\n", + " - [ ] вызывается перед запуском каждого тестового метода\n", + " - [ ] используется для объявления тестовых методов" + ] + }, + { + "cell_type": "markdown", + "id": "bd80e153", + "metadata": {}, + "source": [ + "##### 7. С помощью pdb можно\n", + "\n", + " - [ ] запускать тесты\n", + " - [ ] пошагово выполнять программу\n", + " - [ ] выводить значения переменных\n", + " - [ ] ставить брейкпоинты" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/3. Отладка и тестирование/Тест по блоку.ipynb b/4. Углубленный Python/3. Отладка и тестирование/Тест по блоку.ipynb new file mode 100644 index 0000000..d0d40dc --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/Тест по блоку.ipynb @@ -0,0 +1,120 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "400513aa", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "de522557", + "metadata": {}, + "source": [ + "1. Для чего нужны контекстные менеджеры?\n", + "\n", + " - [ ] Они управляют переключением контекста между модулями\n", + " - [x] Они используются для определения логики в начале и конце блока кода\n", + " - [ ] Они управляют переключением контекста между функциями" + ] + }, + { + "cell_type": "markdown", + "id": "ca2f711d", + "metadata": {}, + "source": [ + "2. Что такое дескриптор?\n", + "\n", + " - [ ] Описание класса\n", + " - [x] Объект с методами `__get__`/`__set__`/`__delete__`\n", + " - [ ] Метод доступа к атрибутам класса\n", + " - [ ] Функция с `yield`" + ] + }, + { + "cell_type": "markdown", + "id": "e8d93626", + "metadata": {}, + "source": [ + "3. Как закончить исполнение итератора?\n", + "\n", + " - [ ] Вернуть `None`\n", + " - [x] Вызвать метод `__exit__`\n", + " - [ ] Выбросить исключение `StopIteration`" + ] + }, + { + "cell_type": "markdown", + "id": "de9f3ce1", + "metadata": {}, + "source": [ + "4. Для чего нужны метаклассы?\n", + "\n", + " - [x] Для управление процессом создания классов\n", + " - [ ] Для создания дескрипторов\n", + " - [ ] Для создания метаобъектов" + ] + }, + { + "cell_type": "markdown", + "id": "b5e7333f", + "metadata": {}, + "source": [ + "5. С помощью какого оператора можно получить значение из `__enter__`?\n", + "\n", + " - [ ] `to`\n", + " - [ ] `from`\n", + " - [x] `as`" + ] + }, + { + "cell_type": "markdown", + "id": "be3f88b9", + "metadata": {}, + "source": [ + "6. Метод `setUp` у наследующегося от `TestCase` класса\n", + "\n", + " - [ ] вызывается перед запуском тестового класса\n", + " - [x] нужен для подготовки данных перед запуском тестов\n", + " - [x] вызывается перед запуском каждого тестового метода\n", + " - [ ] используется для объявления тестовых методов" + ] + }, + { + "cell_type": "markdown", + "id": "bd80e153", + "metadata": {}, + "source": [ + "7. С помощью pdb можно\n", + "\n", + " - [ ] запускать тесты\n", + " - [x] пошагово выполнять программу\n", + " - [x] выводить значения переменных\n", + " - [x] ставить брейкпоинты" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/3. Отладка и тестирование/Тестирование.ipynb b/4. Углубленный Python/3. Отладка и тестирование/Тестирование.ipynb new file mode 100644 index 0000000..7e8461d --- /dev/null +++ b/4. Углубленный Python/3. Отладка и тестирование/Тестирование.ipynb @@ -0,0 +1,325 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "818629e5", + "metadata": {}, + "source": [ + "# Тестирование #" + ] + }, + { + "cell_type": "markdown", + "id": "fc9a38b8", + "metadata": {}, + "source": [ + "Привет. Настало время поговорить о тестировании, которого так бояться многие программисты.\n", + "\n", + "Допустим вы написали программу. Как же проверить, что она работает корректно? Вы можете подать ей на вход какие-то данные и посмотреть, что получается на выходе, действительно ли это то, чего вы ожидаете. Однако, допустим вы изменили вашу программу или что, если вы работаете над большим проектом, с большим количеством разработчиков и туда постоянно вносятся изменения. Вам нужно постоянно проверять правильно ли работает ваша программа в различных условиях. Именно это и называется тестированием. На самом деле тестированию можно посвятить отдельную тему, отдельный курс, отдельную специализацию, потому что это огромная область.\n", + "\n", + "Мы с вами разберем наиболее популярный, наиболее распространенный вид тестирования - это `unit` тестирование." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d02bc71c", + "metadata": {}, + "outputs": [], + "source": [ + "# test_python.py\n", + "\n", + "import unittest\n", + "\n", + "class TestPython(unittest.TestCase):\n", + " def test_float_to_int_coercion(self):\n", + " self.assertEqual(1, int(1.0))\n", + " \n", + " def test_get_empty_dict(self):\n", + " self.assertIsNone({}.get(\"key\"))\n", + " \n", + " def test_trueness(self):\n", + " self.assertTrue(bool(10))" + ] + }, + { + "cell_type": "markdown", + "id": "992814e4", + "metadata": {}, + "source": [ + "`Unit` тесты призваны протестировать какую-то небольшую функциональность, функцию, класс или модуль, посмотреть корректно ли он работает. Вы можете написать `unit` тесты к вашем классу, чтобы проверять все ли он делает корректно. Чтобы определить свой `unittest` можно воспользоваться стандартной библиотекой модулей `unittest` и определить свой класс, который наследуется от `TestCase`'а из модуля `unittest`. Дальше вы можете определить функции, которые, собственно, и будут являться тестами. Каждая функция, которая начинается с `test` и нижнего подчеркивания, является тестом и внутри этого теста вы можете проверить какие-то условия. В данном случае мы можем проверить правильно ли у нас приводится тип в случае `int`'a и `float`'а, или, например, корректно ли у нас работает функция `get` у пустого словаря. Делается это с помощью методов `TestCase`'а. Их довольно много - есть `assertEqual`, `assertIsNone`, `assertRaises` и так далее. Вы можете посмотреть про это в документации. Все они делают одно - они проверяют корректно ли работает выражение, корректно ли вызывается функция и так далее.\n", + "\n", + "Чтобы запустить тесты можно воспользоваться консолью. Еще чаще тесты запускает какая-то автоматическая система сборки или тестирования, или, например, ваше IDE может запускать тесты. Давайте перейдем в консоль и давайте запустим наши тесты. В данном случае мы тестируем Python, проверяем корректно ли он работает. Давайте запустим тесты." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4b1f75b5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "...\r\n", + "----------------------------------------------------------------------\r\n", + "Ran 3 tests in 0.000s\r\n", + "\r\n", + "OK\r\n" + ] + } + ], + "source": [ + "! python -m unittest test_python.py" + ] + }, + { + "cell_type": "markdown", + "id": "81c9b6ff", + "metadata": {}, + "source": [ + "Наши тесты прошли. Об этом говорят три точки и надпись, что три теста прошло. Замечательно.\n", + "\n", + "Если б у нас какой-то тест упал, было бы что-то по-другому. Давайте посмотрим, например, когда у нас падает наш тест." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f3b615cb", + "metadata": {}, + "outputs": [], + "source": [ + "# test_division.py\n", + "\n", + "import unittest\n", + "\n", + "class TestDivision(unittest.TestCase):\n", + " def test_integer_division(self):\n", + " self.assertIs(10 / 5, 2)" + ] + }, + { + "cell_type": "markdown", + "id": "c2c03577", + "metadata": {}, + "source": [ + "Мы определяем тест на деление, и вы можете заметить, что действительно скорее всего он упадет, потому что при делении двух целых чисел в Python'е 3 получается `float`, а не `int`. Давайте запустим тестирование деления." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c51994b7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F\r\n", + "======================================================================\r\n", + "FAIL: test_integer_division (test_division.TestDivision)\r\n", + "----------------------------------------------------------------------\r\n", + "Traceback (most recent call last):\r\n", + " File \"/home/mikhaylovaf/projects/python/4. Углубленный Python/3. Отладка и тестирование/test_division.py\", line 5, in test_integer_division\r\n", + " self.assertIs(10 / 5, 2)\r\n", + "AssertionError: 2.0 is not 2\r\n", + "\r\n", + "----------------------------------------------------------------------\r\n", + "Ran 1 test in 0.001s\r\n", + "\r\n", + "FAILED (failures=1)\r\n" + ] + } + ], + "source": [ + "! python -m unittest test_division.py" + ] + }, + { + "cell_type": "markdown", + "id": "04307ce5", + "metadata": {}, + "source": [ + "Да, наш тест упал. Об этом говорит буква `F` и описание, собственно, падения. Можем посмотреть, что у нас упала функция `test_integer_division`, и с каким `AssertionError`'ом конкретно она упала. Собственно, если упал тест, вы всегда можете посмотреть почему и исправить вашу функциональность." + ] + }, + { + "cell_type": "markdown", + "id": "7eec0ffa", + "metadata": {}, + "source": [ + "Давайте посмотрим на конкретный пример и напишем свой собственный класс, который попробуем протестировать. Класс будет называться `Asteroid` и он призван помочь нам работать с открытым `api NASA` по астероидам и каким-то телам, которые летают вокруг Земли. Давайте определим наш класс и передадим туда уникальный идентификатор астероида, который мы хотим исследовать, данные о котором мы хотим узнать. Запишем соответствующий `api_url` и будем использовать функцию `get_data`, которая идет в Интернет в `api` и забирает информацию с сайта `NASA`. Информация в `json`'е, поэтому очень легко с этим работать. Мы воспользуемся библиотекой `requests` и просто будем возвращать из `get_data` какой-то словарь данных. Дальше мы можем определить различные методы, и в данном случае `property`, которые возвращают данные про наш астероид. В данном случае мы можем получить его имя или его размер в метрах с помощью функции `diameter`. Опять же вы можете заметить, что мы каждый раз вызываем функцию `get_data` и каждый раз идем в Интернет. Можно это оптимизировать и ходить в Интернет один раз, это действительно так. Давайте протестируем наш класс и посмотрим, корректно ли работает наша функция `name` и наша функция `diameter`. Однако, вы можете заметить, что здесь есть некоторая тонкость. Каждый раз, когда мы будем запускать тесты, у нас наш класс, `TestCase` будет ходить в Интернет, потому что у нас запускается функция `get_data`. Это не всегда будет работать, потому что мы можем запускать наши тесты, например, в окружении, в котором нет Интернета, или Интернет медленный, или мы экономим трафик. Точно то же самое можно сказать про работу с Сетью в принципе или про работу с какими-то другими ресурсами, например, с диском. Возможно мы не хотим загружать диск. Что же сделать в таком случае? На помощь нам придет несколько механизмов, о которых мы поговорим прямо сейчас." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e78c44bb", + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "\n", + "class Asteroid:\n", + " BASE_API_URL = \"https://api.nasa.gov/neo/rest/v1/neo/{}?api_key=DEMO_KEY\"\n", + " \n", + " def __init__(self, spk_id):\n", + " self.api_url = self.BASE_API_URL.format(spk_id)\n", + " \n", + " def get_data(self):\n", + " return requests.get(self.api_url).json()\n", + " \n", + " @property\n", + " def name(self):\n", + " return self.get_data()[\"name\"]\n", + " \n", + " @property\n", + " def diameter(self):\n", + " return int(self.get_data()[\"estimated_diameter\"][\"meters\"][\"estimated_diameter_max\"])\n", + " \n", + " @property\n", + " def closest_approach(self):\n", + " closest = {\n", + " \"date\": None,\n", + " \"distance\": float(\"inf\")\n", + " }\n", + " \n", + " for approach in self.get_data()[\"close_approach_data\"]:\n", + " distance = float(approach[\"miss_distance\"][\"lunar\"])\n", + " if distance < closest[\"distance\"]:\n", + " closest.update({\n", + " \"date\": approach[\"close_approach_date\"],\n", + " \"distance\": distance\n", + " })\n", + " \n", + " return closest" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f93f4f7", + "metadata": {}, + "outputs": [], + "source": [ + "apophis = Asteroid(2099942)\n", + "\n", + "print(f\"Name: {apophis.name}\")\n", + "print(f\"Diameter: {apophis.diameter}m\")\n", + "\n", + "print(f\"Date: {apophis.closest_approach['date']}\")\n", + "print(f\"Distance: {apophis.closest_approach['distance']:.2} LD\")" + ] + }, + { + "cell_type": "markdown", + "id": "5fb55d0d", + "metadata": {}, + "source": [ + "Итак, давайте напишем наш `TestCase` и тестировать мы будем астероид с таким вот `id`'шником. Это астероид `apophis`, про который было много шума совсем недавно. Довольно большой астероид, который довольно часто пролетает мимо Земли. Итак, напишем наш `TestCase` и познакомимся с некоторыми новыми моментами." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3daa47dc", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import unittest\n", + "from unittest.mock import patch\n", + "\n", + "from asteroid import Asteroid\n", + "\n", + "class TestAsteroid(unittest.TestCase):\n", + " def setUp(self):\n", + " self.asteroid = Asteroid(2099942)\n", + " \n", + " def mocked_get_data(self):\n", + " with open(\"apophis_fixture.txt\") as f:\n", + " return json.loads(f.read())\n", + " \n", + " @patch(\"asteroid.Asteroid.get_data\", mocked_get_data)\n", + " def test_name(self):\n", + " self.assertEqual(\n", + " self.asteroid.name, \"99942 Apophis (2004 MN4)\"\n", + " )\n", + " \n", + " @patch(\"asteroid.Asteroid.get_data\", mocked_get_data)\n", + " def test_diameter(self):\n", + " self.assertEqual(self.asteroid.diameter, 682)" + ] + }, + { + "cell_type": "markdown", + "id": "2185e4b3", + "metadata": {}, + "source": [ + "Итак, во-первых, мы опять же определяем класс, который наследуется от `TestCase`'а и определяем новую функцию `setUp`, с который вы еще не знакомы. Функция `setUp` призвана, как и соответствует ее название, \"засетапить\" окружение, которое будет работать во время исполнения тестовой функции. Таким образом, если нам нужно работать, например, с объектом `Asteroid`'а, мы можем в начале исполнения каждой функции создавать этот объект `Asteroid`'а, чтобы не дублировать этот код каждый раз в начале наших тестовых функций, или мы можем создавать другие какие-то объекты или как-то наши данные готовить. Существует симметричный метод, который называется `tearDown`, который позволяет закрывать какие-то ресурсы, удалять объекты в конце каждой тестовой функции.\n", + "\n", + "Итак, давайте напишем наши две тестовые функции, которые будут проверять `test_name`, то есть `diameter`. Однако, что если мы тестируем наши функции в окружении без Интернета, как я вам уже говорил. На помощь нам может прийти механизм `mock`'ов и модуль `unittest.mock`, который позволяет подменять какую-то функциональность, подменять какие-то функции другими. Таким образом, мы можем на самом деле не ходить в Интернет, а можем, например, читать информацию из файла. Я заранее скачал данные об астероиде `Apophis` в специальную фикстуру, текстовый файл. Это просто точно то же самое, что и возвращает наш `api`, только оно лежит в файле. Мы подменим функцию, которая идет в Интернет `get_data` функцией, которая просто читает из файла. Таким образом мы не будет ходить в Интернет во время тестов. Делается это с помощью декоратора `patch` довольно просто. Мы можем проверять внутри нашей тестовой функции определенные условия. В данном случае мы проверяем действительно ли имя астероида, которое мы распарсили, в данном случае из файла, действительно оно корректно, действительно ли оно `Apophis` и проверять размер нашего астероида - действительно ли он равен почти 700 метрам.\n", + "\n", + "Итак, давайте запустим наш `TestCase`. Сделаем это точно также:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dfc3003d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "..\r\n", + "----------------------------------------------------------------------\r\n", + "Ran 2 tests in 0.003s\r\n", + "\r\n", + "OK\r\n" + ] + } + ], + "source": [ + "! python -m unittest test_asteroid.py" + ] + }, + { + "cell_type": "markdown", + "id": "797af4b7", + "metadata": {}, + "source": [ + "Да, наши тесты прошли, все замечательно, наш класс работает корректно. Скорее всего вручную тесты вы не будете запускать. Это будет запускать какая-то автоматическая система.\n", + "\n", + "Существует также у `unittest`'а возможность автоматического нахождения тестов, которые лежат, например, в директории `tests`. Очень редко приходится вручную запускать конкретные файлы с тестами.\n", + "\n", + "Итак, что же мы сделали? Мы написали тест для нашего класса. Мы действительно теперь знаем, что у нас наши атрибуты `name` и `diameter` работают корректно. Что ж, мы научились тестировать наш код, и пожалуйста пишите тесты и не бойтесь этого. Удачи." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/4. Углубленный Python/Readme.ipynb b/4. Углубленный Python/Readme.ipynb new file mode 100644 index 0000000..fcfa37d --- /dev/null +++ b/4. Углубленный Python/Readme.ipynb @@ -0,0 +1,104 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2c780f7c", + "metadata": {}, + "source": [ + "# Углубленный Python #\n", + "\n", + "В этом блоке мы более подробно познакомимся с тем, как работают классы в Python. Узнаем, как создавать свои классы, которые поддерживают стандартные протоколы и методы. Научимся отлаживать и тестировать свои программы." + ] + }, + { + "cell_type": "markdown", + "id": "356f5954", + "metadata": {}, + "source": [ + "## Задачи обучения ##\n", + "\n", + "- Изучить углубленные особенности объектно-ориентированной модели в Python.\n", + "- Научиться искать и исправлять ошибки в программе на Python.\n", + "- Освоить тестирование программ на Python." + ] + }, + { + "cell_type": "markdown", + "id": "33438bb7", + "metadata": {}, + "source": [ + "## Оглавление ##" + ] + }, + { + "cell_type": "markdown", + "id": "90a1e5ab", + "metadata": {}, + "source": [ + "### Особые методы классов ###\n", + "\n", + "- [Магические методы](1.%20Особые%20методы%20классов/Магические%20методы.ipynb)\n", + "- [Итераторы](1.%20Особые%20методы%20классов/Итераторы.ipynb)\n", + "- [Контекстные менеджеры](1.%20Особые%20методы%20классов/Контекстные%20менеджеры.ipynb)\n", + "- [Документация](1.%20Особые%20методы%20классов/Документация.ipynb)\n", + "- [Тест по методам](1.%20Особые%20методы%20классов/Тест%20по%20методам.ipynb)\n", + "- [Файл с магическими методами](1.%20Особые%20методы%20классов/Файл%20с%20магическими%20методами.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "e8f133e3", + "metadata": {}, + "source": [ + "### Механизм работы классов ###\n", + "\n", + "- [Дескрипторы](2.%20Механизм%20работы%20классов/Дескрипторы.ipynb)\n", + "- [Метаклассы](2.%20Механизм%20работы%20классов/Метаклассы.ipynb)\n", + "- [Документация](2.%20Механизм%20работы%20классов/Документация.ipynb)\n", + "- [Дескриптор с комиссией](2.%20Механизм%20работы%20классов/Дескриптор%20с%20комиссией.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "480a600c", + "metadata": {}, + "source": [ + "### Отладка и тестирование ###\n", + "\n", + "- [Отладка](3.%20Отладка%20и%20тестирование/Отладка.ipynb)\n", + "- [Тестирование](3.%20Отладка%20и%20тестирование/Тестирование.ipynb)\n", + "- [Документация](3.%20Отладка%20и%20тестирование/Документация.ipynb)\n", + "- [Тест по блоку](3.%20Отладка%20и%20тестирование/Тест%20по%20блоку.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "c14185e2", + "metadata": {}, + "source": [ + "Закончился четвертый блок нашего курса. Мы с вами разобрали как на самом деле работают классы в Python'е, как реализована объектно-ориентированная парадигма в языке. Мы с вами узнали, что такое дескрипторы, метаклассы, как создавать собственные контекстные менеджеры, итераторы, как определять классы с переопределенным поведением. В следующем блоке вы познакомитесь с асинхронным и многопоточным программированием в Python'е. [Далее...](../5.%20Многопоточное%20и%20асинхронное%20программирование/Readme.ipynb)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/data.txt b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/data.txt new file mode 100644 index 0000000..13ddde0 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/data.txt @@ -0,0 +1,2 @@ +example string1 +example string2 \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex1.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex1.py new file mode 100644 index 0000000..4e4d9f4 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex1.py @@ -0,0 +1,8 @@ +# простой Python процесс +import time +import os + +pid = os.getpid() +while True: + print(pid, time.time()) + time.sleep(2) \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex2.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex2.py new file mode 100644 index 0000000..3115c13 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex2.py @@ -0,0 +1,15 @@ +# Создание процесса на Python +import time +import os + +pid = os.fork() + +if pid == 0: + # дочерний процесс + while True: + print(f"child: {os.getpid()}" ) + time.sleep(5) +else: + # родительский процесс + print(f"parent: {os.getpid()}") + os.wait() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex3.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex3.py new file mode 100644 index 0000000..52ba553 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex3.py @@ -0,0 +1,14 @@ +# Память родительского и дочернего процесса +import os + +foo = "bar" + +if os.fork() == 0: + # дочерний процесс + foo = "baz" + print("child:", foo) + +else: + # родительский процесс + print("parent:", foo) + os.wait() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex4.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex4.py new file mode 100644 index 0000000..0136d91 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex4.py @@ -0,0 +1,18 @@ +# Файлы в родительском и дочернем процессе +# $ cat data.txt +# example string1 +# example string2 + +import os +f = open("data.txt") +foo = f.readline() + +if os.fork() == 0: + # дочерний процесс + foo = f.readline() + print(f"child: {foo}") + +else: + # родительский процесс + foo = f.readline() + print(f"parent: {foo}") \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex5.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex5.py new file mode 100644 index 0000000..b1cae86 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex5.py @@ -0,0 +1,9 @@ +# Создание процесса, модуль multiprocessing +from multiprocessing import Process + +def f(name): + print(f"hello {name}") + +p = Process(target=f, args=("Bob",)) +p.start() +p.join() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex6.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex6.py new file mode 100644 index 0000000..beb747c --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex6.py @@ -0,0 +1,14 @@ +# Создание процесса, модуль multiprocessing +from multiprocessing import Process + +class PrintProcess(Process): + def __init__(self, name): + super().__init__() + self.name = name + + def run(self): + print(f"hello {self.name}") + +p = PrintProcess("Mike") +p.start() +p.join() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex7.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex7.py new file mode 100644 index 0000000..564e784 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex7.py @@ -0,0 +1,9 @@ +# Создание потока +from threading import Thread + +def f(name): + print(f"hello {name}") + +th = Thread(target=f, args=("Bob",)) +th.start() +th.join() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex8.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex8.py new file mode 100644 index 0000000..b264e56 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex8.py @@ -0,0 +1,14 @@ +# Создание потока +from threading import Thread + +class PrintThread(Thread): + def __init__(self, name): + super().__init__() + self.name = name + + def run(self): + print(f"hello {self.name}") + +th = PrintThread("Mike") +th.start() +th.join() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex9.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex9.py new file mode 100644 index 0000000..f10a5a6 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex9.py @@ -0,0 +1,12 @@ +# Пул потоков, concurrent.futures.Future +from concurrent.futures import ThreadPoolExecutor, as_completed + +def f(a): + return a * a + +# .shutdown() in exit +with ThreadPoolExecutor(max_workers=3) as pool: + results = [pool.submit(f, i) for i in range(10)] + + for future in as_completed(results): + print(future.result()) \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex_gil.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex_gil.py new file mode 100644 index 0000000..6acaaf2 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex_gil.py @@ -0,0 +1,23 @@ +# cpu bound programm + +from threading import Thread +import time + +def count(n): + while n > 0: + n -= 1 + +# series run +t0 = time.time() +count(100_000_000) +count(100_000_000) +print(time.time() - t0) + +# parallel run +t0 = time.time() +th1 = Thread(target=count, args=(100_000_000,)) +th2 = Thread(target=count, args=(100_000_000,)) + +th1.start(); th2.start() +th1.join(); th2.join() +print(time.time() - t0) \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex_queue.py b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex_queue.py new file mode 100644 index 0000000..81476dd --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/ex_queue.py @@ -0,0 +1,21 @@ +# Очереди, модуль queue +from queue import Queue +from threading import Thread + +def worker(q, n): + while True: + item = q.get() + if item is None: + break + print(f"process data: {n} {item}") + +q = Queue(5) +th1 = Thread(target=worker, args=(q, 1)) +th2 = Thread(target=worker, args=(q, 2)) +th1.start(); th2.start() + +for i in range(50): + q.put(i) + +q.put(None); q.put(None) +th1.join(); th2.join() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/log.txt b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/log.txt new file mode 100644 index 0000000..6bfab86 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/log.txt @@ -0,0 +1,147 @@ +19347 1642063085.5886922 +19347 1642063087.590815 +19347 1642063089.5929003 +19347 1642063091.594989 +19347 1642063093.5970716 +19347 1642063095.5991616 +19347 1642063097.6012437 +19347 1642063099.6035538 +19347 1642063101.6056507 +19347 1642063103.6077416 +19347 1642063105.609838 +19347 1642063107.6119277 +19347 1642063109.614012 +19347 1642063111.6161063 +19347 1642063113.6182148 +19347 1642063115.6202853 +19347 1642063117.6223767 +19347 1642063119.6244748 +19347 1642063121.6265666 +19347 1642063123.6286826 +19347 1642063125.630799 +19347 1642063127.632922 +19347 1642063129.635037 +19347 1642063131.6371615 +19347 1642063133.639279 +19347 1642063135.6414008 +19347 1642063137.6435366 +19347 1642063139.6456687 +19347 1642063141.6477964 +19347 1642063143.6499193 +19347 1642063145.6520364 +19347 1642063147.6541586 +19347 1642063149.6562767 +19347 1642063151.6583996 +19347 1642063153.660516 +19347 1642063155.6626382 +19347 1642063157.6647534 +19347 1642063159.6668763 +19347 1642063161.6689947 +19347 1642063163.6711178 +19347 1642063165.6732345 +19347 1642063167.6761174 +19347 1642063169.6782281 +19347 1642063171.6805048 +19347 1642063173.6826222 +19347 1642063175.6847298 +19347 1642063177.6868467 +19347 1642063179.6889684 +19347 1642063181.6910837 +19347 1642063183.6931915 +19347 1642063185.6953082 +19347 1642063187.697434 +19347 1642063189.6995487 +19347 1642063191.7016575 +19347 1642063193.703773 +19347 1642063195.705896 +19347 1642063197.708012 +19347 1642063199.710125 +19347 1642063201.7122993 +19347 1642063203.7144253 +19347 1642063205.7165427 +19347 1642063207.718665 +19347 1642063209.7208102 +19347 1642063211.7229385 +19347 1642063213.725056 +19347 1642063215.7271628 +19347 1642063217.729282 +19347 1642063219.7314017 +19347 1642063221.7334661 +19347 1642063223.7355802 +19347 1642063225.7376955 +19347 1642063227.739816 +19347 1642063229.74193 +19347 1642063231.744057 +19347 1642063233.7461717 +19347 1642063235.7482917 +19347 1642063237.7504082 +19347 1642063239.7525218 +19347 1642063241.754639 +19347 1642063243.7567587 +19347 1642063245.758894 +19347 1642063247.7610009 +19347 1642063249.7631152 +19347 1642063251.765237 +19347 1642063253.7679825 +19347 1642063255.7701032 +19347 1642063257.772209 +19347 1642063259.7745354 +19347 1642063261.7766626 +19347 1642063263.778784 +19347 1642063265.7808979 +19347 1642063267.7830167 +19347 1642063269.7851312 +19347 1642063271.7875907 +19347 1642063273.7897053 +19347 1642063275.7918243 +19347 1642063277.7939389 +19347 1642063279.7960577 +19347 1642063281.798172 +19347 1642063283.8002877 +19347 1642063285.8024018 +19347 1642063287.8045204 +19347 1642063289.8066363 +19347 1642063291.808783 +19347 1642063293.8108954 +19347 1642063295.8130164 +19347 1642063297.815131 +19347 1642063299.817533 +19347 1642063301.8196485 +19347 1642063303.8217676 +19347 1642063305.8238776 +19347 1642063307.8259828 +19347 1642063309.828097 +19347 1642063311.8302102 +19347 1642063313.8327134 +19347 1642063315.8348327 +19347 1642063317.836977 +19347 1642063319.8390949 +19347 1642063321.841223 +19347 1642063323.8433912 +19347 1642063325.8455067 +19347 1642063327.8476112 +19347 1642063329.849725 +19347 1642063331.8518434 +19347 1642063333.8539565 +19347 1642063335.8560681 +19347 1642063337.8581808 +19347 1642063339.8602993 +19347 1642063341.8624122 +19347 1642063343.8645294 +19347 1642063345.8666444 +19347 1642063347.8687632 +19347 1642063349.870879 +19347 1642063351.8729925 +19347 1642063353.8751054 +19347 1642063355.8772247 +19347 1642063357.879487 +19347 1642063359.8816047 +19347 1642063361.8837194 +19347 1642063363.8858376 +19347 1642063365.8879495 +19347 1642063367.890068 +19347 1642063369.8922114 +19347 1642063371.8947868 +19347 1642063373.8969002 +19347 1642063375.899018 +19347 1642063377.9011319 diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Глобальная блокировка интерпретатора.ipynb b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Глобальная блокировка интерпретатора.ipynb new file mode 100644 index 0000000..c7ccbf7 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Глобальная блокировка интерпретатора.ipynb @@ -0,0 +1,182 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "437382d2", + "metadata": {}, + "source": [ + "# Глобальная блокировка интерпретатора #" + ] + }, + { + "cell_type": "markdown", + "id": "edf47592", + "metadata": {}, + "source": [ + "На этой лекции мы поговорим о том, что такое глобальная блокировка интерпретатора, или, как ее сокращенно иногда называют, `GIL`.\n", + "\n", + "`GIL` очень тесно связан с выполнением потоков, и для более глубокого понимания того, как работают потоки в Python, нужны общие сведения о том, как устроен и как работает `GIL` в Python. Многие разработчики знают о `GIL` лишь то, что это какая-то штука, которая не позволяет одновременно двум потокам выполняться на одном ядре процессора, даже если этих ядер у процессора несколько. Тем не менее, `GIL` в первую очередь предназначен для защиты памяти интерпретатора от разрушений и делает все операции с памятью атомарными.\n", + "\n", + "Давайте рассмотрим следующую программу, которую я представил на слайде, и сразу запустим ее выполнение в консоли, потому что выполнение займет некоторое время." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "540fbcd9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "25.041810989379883\n", + "71.44423460960388\n" + ] + } + ], + "source": [ + "# cpu bound programm\n", + "\n", + "from threading import Thread\n", + "import time\n", + "\n", + "def count(n):\n", + " while n > 0:\n", + " n -= 1\n", + " \n", + "# series run\n", + "t0 = time.time()\n", + "count(100_000_000)\n", + "count(100_000_000)\n", + "print(time.time() - t0)\n", + "\n", + "# parallel run\n", + "t0 = time.time()\n", + "th1 = Thread(target=count, args=(100_000_000,))\n", + "th2 = Thread(target=count, args=(100_000_000,))\n", + "\n", + "th1.start(); th2.start()\n", + "th1.join(); th2.join()\n", + "print(time.time() - t0)" + ] + }, + { + "cell_type": "markdown", + "id": "ddfd23f8", + "metadata": {}, + "source": [ + "Итак, вот наш пример. Запускаем его. Можем посмотреть при помощи команды top, что этот пример потребляет определенное количество центрального процессора. Да, видим, действительно, наш интерпретатор потребляет почти 100% CPU на одном ядре." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5444e55f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[?1h\u001b=\u001b[H\u001b[2J\u001b[mtop - 15:30:28 up 415 days, 7:20, 1 user, load average: 0.73, 0.33, 0.19\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "Tasks:\u001b[m\u001b[m\u001b[1m 314 \u001b[m\u001b[mtotal,\u001b[m\u001b[m\u001b[1m 2 \u001b[m\u001b[mrunning,\u001b[m\u001b[m\u001b[1m 312 \u001b[m\u001b[msleeping,\u001b[m\u001b[m\u001b[1m 0 \u001b[m\u001b[mstopped,\u001b[m\u001b[m\u001b[1m 0 \u001b[m\u001b[mzombie\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "%Cpu(s):\u001b[m\u001b[m\u001b[1m 4.8 \u001b[m\u001b[mus,\u001b[m\u001b[m\u001b[1m 0.7 \u001b[m\u001b[msy,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[mni,\u001b[m\u001b[m\u001b[1m 94.5 \u001b[m\u001b[mid,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[mwa,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[mhi,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[msi,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[mst\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "KiB Mem :\u001b[m\u001b[m\u001b[1m 65805548 \u001b[m\u001b[mtotal,\u001b[m\u001b[m\u001b[1m 23703044 \u001b[m\u001b[mfree,\u001b[m\u001b[m\u001b[1m 1669436 \u001b[m\u001b[mused,\u001b[m\u001b[m\u001b[1m 40433068 \u001b[m\u001b[mbuff/cache\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "KiB Swap:\u001b[m\u001b[m\u001b[1m 33030140 \u001b[m\u001b[mtotal,\u001b[m\u001b[m\u001b[1m 33030140 \u001b[m\u001b[mfree,\u001b[m\u001b[m\u001b[1m 0 \u001b[m\u001b[mused.\u001b[m\u001b[m\u001b[1m 63027376 \u001b[m\u001b[mavail Mem \u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "\u001b[K\n", + "\u001b[7m PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND \u001b[m\u001b[m\u001b[K\n", + "\u001b[m\u001b[1m19526 mikhayl+ 20 0 128124 6996 2748 R 100.0 0.0 0:05.20 python \u001b[m\u001b[m\u001b[K\n", + "\u001b[m19506 mikhayl+ 20 0 921208 45756 7872 S 11.8 0.1 3:14.42 python3 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m15053 snaga 20 0 288136 37196 6792 S 5.9 0.1 16:41.20 uwsgi \u001b[m\u001b[m\u001b[K\n", + "\u001b[m17818 mikhayl+ 20 0 498940 69812 8296 S 5.9 0.1 4:34.57 jupyter-no+ \u001b[m\u001b[m\u001b[K\n", + "\u001b[m\u001b[1m19527 mikhayl+ 20 0 157848 2212 1436 R 5.9 0.0 0:00.05 top \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 1 root 20 0 191788 4784 2420 S 0.0 0.0 20:00.79 systemd \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 2 root 20 0 0 0 0 S 0.0 0.0 0:04.01 kthreadd \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 3 root 20 0 0 0 0 S 0.0 0.0 0:00.62 ksoftirqd/0 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 5 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:+ \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 8 root rt 0 0 0 0 S 0.0 0.0 0:00.05 migration/0 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 9 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 10 root 20 0 0 0 0 S 0.0 0.0 20:58.21 rcu_sched \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 11 root rt 0 0 0 0 S 0.0 0.0 2:53.71 watchdog/0 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 12 root rt 0 0 0 0 S 0.0 0.0 2:42.51 watchdog/1 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 13 root rt 0 0 0 0 S 0.0 0.0 0:01.13 migration/1 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 14 root 20 0 0 0 0 S 0.0 0.0 0:00.66 ksoftirqd/1 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 16 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/1:+ \u001b[m\u001b[m\u001b[K\u001b[?1l\u001b>\u001b[25;1H\n", + "\u001b[K" + ] + } + ], + "source": [ + "! top" + ] + }, + { + "cell_type": "markdown", + "id": "0a282bd2", + "metadata": {}, + "source": [ + "Давайте вернемся к программе и рассмотрим, что она делает. Прежде всего, у нас есть функция `count`, которая в цикле уменьшает значение счетчика до нуля. Нам необходимо выполнить два вызова этой функции со значением `100_000_000` и засечь, сколько времени займет выполнение двух этих функций с этим счетчиком. То есть функция потребляет только центральный процессор. Она не делает никаких операций ввода-вывода, не ходит в сеть. Для сравнения мы можем выполнить эту функцию в потоке. Создадим два потока при помощи уже известных нам ранее методов модуля `threading`. Передадим туда эту функцию, те же самые аргументы, запустим наши потоки, подождем, пока они завершатся при помощи метода `join`, и выведем количество секунд, которое было потрачено на выполнение работы этих двух потоков.\n", + "\n", + "Давайте вернемся в консоль и посмотрим на результаты работы наших функций. Мы видим, что параллельное выполнение при помощи потоков заняло больше времени. Как же так? Тогда зачем нужны потоки вообще, и почему так происходит?\n", + "\n", + "Всё дело как раз здесь в глобальной блокировке интерпретатора. Дело в том, что потоки при выполнении своего кода каждый раз получают блокировку интерпретатора. Если у нас задача `CPU bound`, так называют задачи, которые потребляют только процессор, то код, написанный с использованием тредов в Python, будет неэффективным. Он будет работать медленнее, чем код, который запущен последовательно. Тем не менее, если мы код нашей функции заменим, например, на задачу, которая требует операции ввода-вывода, то мы заметим большой прирост в итоговом времени выполнения, если сравнивать параллельное выполнение и выполнение в тредах." + ] + }, + { + "cell_type": "markdown", + "id": "be510455", + "metadata": {}, + "source": [ + "```\n", + "# как выполняется поток?\n", + "\n", + "a r a r a r a\n", + " run |------| run |--------------| run |----| run\n", + "------>| IO |-------->| IO |-------->| IO |----->\n", + " |------| |--------------| |----|\n", + "a r a r a r a\n", + "\n", + "a - acquire GIL\n", + "r - release GIL\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "0cc602c7", + "metadata": {}, + "source": [ + "Если изобразить схематично, как выполняется поток, то выглядит это следующим образом. У нас есть поток, в котором выполняется наш Python код, и каждый раз Python интерпретатор пробует получить глобальную блокировку интерпретатора. Если Python выполняет операцию ввода-вывода или системный вызов, то он эту блокировку снимает, и далее выполнение происходит без блокировки. Поэтому если у нас таких будет потоков много, все задачи с вводом-выводом, с ожиданием завершения для операции ввода-вывода будут очень хорошо параллелиться. Это нужно учитывать в своих задачах, если вы будете применять потоки или процессы.\n", + "\n", + "`GIL` внутри реализован как обычная нерекурсивная блокировка, или объект класса `threading lock`. Все потоки спят пять миллисекунд в ожидании получения блокировки, и в Python 3, если работает один главный поток, то он не требует освобождения этой глобальной блокировки интерпретатора.\n", + "\n", + "Итак, на этой лекции мы обсудили, что такое `GIL` и какое он отношение имеет к потокам в Python. Так, Python потоки — это обычные потоки, или `POSIX threads`, но с ограничениями в виде глобальной блокировки интерпретатора. Все потоки выполняются с захватом `GIL`, но системные вызовы и операции ввода-вывода, для них `GIL` не нужен.\n", + "\n", + "Итак, мы рассмотрели вопросы про то, как работают потоки и процессы, и в следующих лекциях мы рассмотрим, как устроены сокеты и как работать с сетью с применением полученных знаний о потоках и процессах." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Документация.ipynb b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Документация.ipynb new file mode 100644 index 0000000..dc0a66d --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Документация.ipynb @@ -0,0 +1,43 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "462b30ba", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "8b0c80a5", + "metadata": {}, + "source": [ + "- [python multiprocessing](https://docs.python.org/3.6/library/multiprocessing.html \"17.2. multiprocessing — Process-based parallelism\")\n", + "- [threading](https://docs.python.org/3.6/library/threading.html \"17.1. threading — Thread-based parallelism\")\n", + "- [concurrent.futures](https://docs.python.org/3/library/concurrent.futures.html \"17.4. concurrent.futures — Launching parallel tasks\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Процесс и его характеристики.ipynb b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Процесс и его характеристики.ipynb new file mode 100644 index 0000000..2103f9d --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Процесс и его характеристики.ipynb @@ -0,0 +1,411 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c89738cc", + "metadata": {}, + "source": [ + "# Процесс и его характеристики #" + ] + }, + { + "cell_type": "markdown", + "id": "1dc01923", + "metadata": {}, + "source": [ + "На этой лекции мы поговорим о том, что такое процесс, узнаем, какие процессы запущены в вашей операционной системе, а также попробуем запустить свой первый Python процесс и поизучаем его характеристики.\n", + "\n", + "Итак, процесс — это программа, которая запущена в оперативной памяти компьютера. Другими словами, процесс — это набор инструкций, которые выполняются последовательно. В общем случае это действительно так, но в реальности всё немного сложнее. Мы будем погружаться в детали по мере обучения. Итак, каждый процесс, который запущен в операционной системе, имеет свои характеристики.\n", + "\n", + "Одна из главных характеристик — это идентификатор процесса, или `PID`. При помощи `PID` можно узнать многое о процессе. Каждый процесс занимает некий объем оперативной памяти. Он запрашивает её у системы, она ему её возвращает и аллоцирует. Также у процесса есть стек. Стек используется для вызова функций, для создания локальных переменных у этих функций. А также у каждого процесса есть список открытых файлов, стандартный ввод и стандартный вывод. В своих примерах я буду использовать операционную систему класса Linux и Python 3.\n", + "\n", + "Давайте попробуем узнать, какие процессы запущены в нашей операционной системе. Для этого нам потребуется консоль и команда `top`. Набираем команду `top`, вводим `enter` и видим результаты в виде таблицы. Команда `top` отображает нам список процессов, которые сейчас функционируют в операционной системе." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6f3f3dc1", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[?1h\u001b=\u001b[H\u001b[2J\u001b[mtop - 13:11:15 up 415 days, 5:01, 1 user, load average: 0.00, 0.01, 0.05\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "Tasks:\u001b[m\u001b[m\u001b[1m 305 \u001b[m\u001b[mtotal,\u001b[m\u001b[m\u001b[1m 1 \u001b[m\u001b[mrunning,\u001b[m\u001b[m\u001b[1m 304 \u001b[m\u001b[msleeping,\u001b[m\u001b[m\u001b[1m 0 \u001b[m\u001b[mstopped,\u001b[m\u001b[m\u001b[1m 0 \u001b[m\u001b[mzombie\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "%Cpu(s):\u001b[m\u001b[m\u001b[1m 0.9 \u001b[m\u001b[mus,\u001b[m\u001b[m\u001b[1m 0.7 \u001b[m\u001b[msy,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[mni,\u001b[m\u001b[m\u001b[1m 98.5 \u001b[m\u001b[mid,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[mwa,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[mhi,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[msi,\u001b[m\u001b[m\u001b[1m 0.0 \u001b[m\u001b[mst\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "KiB Mem :\u001b[m\u001b[m\u001b[1m 65805548 \u001b[m\u001b[mtotal,\u001b[m\u001b[m\u001b[1m 23929780 \u001b[m\u001b[mfree,\u001b[m\u001b[m\u001b[1m 1447684 \u001b[m\u001b[mused,\u001b[m\u001b[m\u001b[1m 40428084 \u001b[m\u001b[mbuff/cache\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "KiB Swap:\u001b[m\u001b[m\u001b[1m 33030140 \u001b[m\u001b[mtotal,\u001b[m\u001b[m\u001b[1m 33030140 \u001b[m\u001b[mfree,\u001b[m\u001b[m\u001b[1m 0 \u001b[m\u001b[mused.\u001b[m\u001b[m\u001b[1m 63250272 \u001b[m\u001b[mavail Mem \u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n", + "\u001b[K\n", + "\u001b[7m PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND \u001b[m\u001b[m\u001b[K\n", + "\u001b[m11923 root 20 0 0 0 0 S 5.9 0.0 0:36.59 kworker/12+ \u001b[m\u001b[m\u001b[K\n", + "\u001b[m17818 mikhayl+ 20 0 495792 68184 8240 S 5.9 0.1 3:52.91 jupyter-no+ \u001b[m\u001b[m\u001b[K\n", + "\u001b[m19235 mikhayl+ 20 0 773876 47736 7784 S 5.9 0.1 0:01.98 python3 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m\u001b[1m19251 mikhayl+ 20 0 157848 2216 1436 R 5.9 0.0 0:00.04 top \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 1 root 20 0 191788 4784 2420 S 0.0 0.0 20:00.34 systemd \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 2 root 20 0 0 0 0 S 0.0 0.0 0:04.01 kthreadd \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 3 root 20 0 0 0 0 S 0.0 0.0 0:00.62 ksoftirqd/0 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 5 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:+ \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 8 root rt 0 0 0 0 S 0.0 0.0 0:00.04 migration/0 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 9 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 10 root 20 0 0 0 0 S 0.0 0.0 20:57.39 rcu_sched \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 11 root rt 0 0 0 0 S 0.0 0.0 2:53.67 watchdog/0 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 12 root rt 0 0 0 0 S 0.0 0.0 2:42.47 watchdog/1 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 13 root rt 0 0 0 0 S 0.0 0.0 0:01.13 migration/1 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 14 root 20 0 0 0 0 S 0.0 0.0 0:00.66 ksoftirqd/1 \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 16 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/1:+ \u001b[m\u001b[m\u001b[K\n", + "\u001b[m 17 root rt 0 0 0 0 S 0.0 0.0 2:42.38 watchdog/2 \u001b[m\u001b[m\u001b[K\u001b[?1l\u001b>\u001b[25;1H\n", + "\u001b[K" + ] + } + ], + "source": [ + "! top" + ] + }, + { + "cell_type": "markdown", + "id": "497b15f7", + "metadata": {}, + "source": [ + "Справа мы видим командную строку, при помощи которой были запущены процессы, и в виде колонок в таблице мы видим характеристики процессов. `PID` — идентификатор процесса, пользователь, из-под которого был запущен процесс. Пользователь определяет права, которые будут доступны этому процессу в операционной системе. Также такие важные колонки, как размер виртуальной и физической памяти. Не будем пока вдаваться в подробности, чем она отличается, а также процентные соотношения использования центрального процессора и памяти в процентах.\n", + "\n", + "Как мы видим, процессов в операционной системе достаточно много, и все они работают параллельно, на первый взгляд, но на самом деле это не так. Планировщик операционной системы выделяет небольшой квант времени каждому процессу, исполняет его и затем происходит переключение между другими процессами. Таким образом, процессы выполняются все последовательно, но из-за того, что квант времени небольшой, нам кажется, что все они выполняются параллельно.\n", + "\n", + "Давайте попробуем запустить наш первый Python процесс и изучим его характеристики средствами операционной системы." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "af56a5b0", + "metadata": {}, + "outputs": [], + "source": [ + "# простой Python процесс\n", + "import time\n", + "import os\n", + "\n", + "pid = os.getpid()\n", + "while True:\n", + " print(pid, time.time())\n", + " time.sleep(2)" + ] + }, + { + "cell_type": "markdown", + "id": "9c56ce33", + "metadata": {}, + "source": [ + "Программа, представленная на слайде, достаточно простая. В ней импортируется пара модулей — `time` и `os`. Затем при помощи вызова функции модуля `os.getpid` мы получаем идентификатор процесса, запоминаем его в переменную `pid` и в бесконечном цикле выводим `pid` нашего процесса, системное время, и спим пару секунд. Давайте попробуем запустить этот код. Посмотрим, как он выполняется в консоли. Я подготовил заранее код. Вот наш код. Исполняем его при помощи команды Python 3." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f5f49ba6", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "19255 1642062336.8886158\n", + "19255 1642062338.8908966\n", + "^C\n", + "Traceback (most recent call last):\n", + " File \"ex1.py\", line 7, in \n", + " time.sleep(2)\n", + "KeyboardInterrupt\n" + ] + } + ], + "source": [ + "! python ex1.py" + ] + }, + { + "cell_type": "markdown", + "id": "ebd43ffd", + "metadata": {}, + "source": [ + "Мы видим, что запустился процесс. Он вывел `pid 19287`, системное время, и продолжает это делать бесконечно в цикле. Давайте попробуем найти наш процесс в списке всех процессов. Для этого нам поможет команда `ps` с флагами `aux`. Подробную информацию о флагах можно посмотреть в документации. Итак, эта команда отобразила нам список всех процессов. Для того чтобы найти конкретно наш процесс, можно его отфильтровать при помощи команды `grep`. Итак, мы видим наш процесс." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e9ea1a7f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mikhayl+ 19287 0.5 0.0 125020 5696 pts/6 S+ 13:28 0:00 python ex1.py\r\n", + "mikhayl+ 19293 0.0 0.0 113128 1176 pts/7 Ss+ 13:29 0:00 /bin/bash -c ps aux | grep 19287\r\n", + "mikhayl+ 19295 0.0 0.0 112660 924 pts/7 S+ 13:29 0:00 grep 19287\r\n" + ] + } + ], + "source": [ + "! ps aux | grep 19287" + ] + }, + { + "cell_type": "markdown", + "id": "329bbdd3", + "metadata": {}, + "source": [ + "Для того чтобы посмотреть название характеристик, можно вывести первую строчку от результатов вывода команды `ps` при помощи вот такой команды" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "f2c50933", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\n", + "mikhayl+ 19287 0.6 0.0 125020 5696 pts/6 S+ 13:28 0:00 python ex1.py\n", + "mikhayl+ 19288 0.0 0.0 113128 1304 pts/7 Ss+ 13:29 0:00 /bin/bash -c ps aux| head -1 ; ps aux | grep 19287\n", + "mikhayl+ 19292 0.0 0.0 112660 924 pts/7 S+ 13:29 0:00 grep 19287\n" + ] + } + ], + "source": [ + "! ps aux| head -1 ; ps aux | grep 19287" + ] + }, + { + "cell_type": "markdown", + "id": "2be4f197", + "metadata": {}, + "source": [ + "Итак, еще раз, наш процесс с `pid`'ом `19287` виден в результатах вывода команды `ps`. Вот его `pid`. Он потребляет немного центрального процессора. А также одна из важных характеристик — это объем физической памяти. Мы видим, что он занимает почти 6 килобайт. Такой маленький процесс, а уже занимает 6 килобайт памяти. Видим командную строчку, при помощи которой он был запущен.\n", + "\n", + "Давайте поймем, какую же последовательность команд выполняет наш процесс. Что он вообще делает? Вообще, процессы, когда выполняются в операционной системе, они делают системные вызовы. Системные вызовы выполняют непосредственно ядро операционной системы, а результаты этих системных вызовов возвращаются к процессу, который их вызвал. Например, вывод в консоль, или стандартный вывод — это системный вызов. Для того чтобы посмотреть, какие системные вызовы делает наш процесс, можно воспользоваться командой `strace`, указать ей `pid` нашего процесса. Нужны дополнительные права для этой команды." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "78edff8a", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "strace: Process 19287 attached\n", + "select(0, NULL, NULL, NULL, {1, 430074}) = 0 (Timeout)\n", + "write(1, \"19287 1642062820.0185373\\n\", 25) = 25\n", + "select(0, NULL, NULL, NULL, {2, 0}) = 0 (Timeout)\n", + "write(1, \"19287 1642062822.0211146\\n\", 25) = 25\n", + "select(0, NULL, NULL, NULL, {2, 0}^C\n", + "strace: Process 19287 detached\n", + " \n" + ] + } + ], + "source": [ + "! strace -p 19287" + ] + }, + { + "cell_type": "markdown", + "id": "cc4fc432", + "metadata": {}, + "source": [ + "В результатах `strace`'а мы как раз видим то, что вызывается системный вызов `write`. У него есть в аргументах файловый дескриптор 1. Это как раз наш стандартный вывод. А также мы видим, что в этот стандартный вывод попадает `pid` нашего процесса и системное время. Также видим, что для вызова `sleep` используются другие дополнительные системные вызовы. Для выхода используем `Ctrl+C`.\n", + "\n", + "Итак, мы видим, что процесс во время исполнения наш общается с операционной системой при помощи системных вызовов. Давайте посмотрим еще на список файлов, которые открыты в нашем процессе. Для этого мы можем воспользоваться командой `lsof`. Опять же, указываем `pid` нашего процесса." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "4bb300a6", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\r\n", + "python 19287 mikhaylovaf cwd DIR 253,2 113 1611044178 /home/mikhaylovaf/projects/python/5. Многопоточное и асинхронное программирование/1. Процессы и потоки\r\n", + "python 19287 mikhaylovaf rtd DIR 253,0 262 64 /\r\n", + "python 19287 mikhaylovaf txt REG 253,0 11328 610823 /usr/bin/python3.6\r\n", + "python 19287 mikhaylovaf mem REG 253,0 106070960 2704 /usr/lib/locale/locale-archive\r\n", + "python 19287 mikhaylovaf mem REG 253,0 2127336 33577165 /usr/lib64/libc-2.17.so\r\n", + "python 19287 mikhaylovaf mem REG 253,0 1139680 33577173 /usr/lib64/libm-2.17.so\r\n", + "python 19287 mikhaylovaf mem REG 253,0 14872 33577199 /usr/lib64/libutil-2.17.so\r\n", + "python 19287 mikhaylovaf mem REG 253,0 19776 33577171 /usr/lib64/libdl-2.17.so\r\n", + "python 19287 mikhaylovaf mem REG 253,0 144792 33577191 /usr/lib64/libpthread-2.17.so\r\n", + "python 19287 mikhaylovaf mem REG 253,0 3144192 34017354 /usr/lib64/libpython3.6m.so.1.0\r\n", + "python 19287 mikhaylovaf mem REG 253,0 164112 33577158 /usr/lib64/ld-2.17.so\r\n", + "python 19287 mikhaylovaf mem REG 253,0 26254 33577463 /usr/lib64/gconv/gconv-modules.cache\r\n", + "python 19287 mikhaylovaf 0u CHR 136,6 0t0 9 /dev/pts/6\r\n", + "python 19287 mikhaylovaf 1u CHR 136,6 0t0 9 /dev/pts/6\r\n", + "python 19287 mikhaylovaf 2u CHR 136,6 0t0 9 /dev/pts/6\r\n" + ] + } + ], + "source": [ + "! lsof -p 19287" + ] + }, + { + "cell_type": "markdown", + "id": "7e2b8b4d", + "metadata": {}, + "source": [ + "Список достаточно большой. Как минимум, когда наш Python интерпретатор запускает нашу программу, открываются дополнительные системные библиотеки. Они отображены тоже в результатах команды `lsof`. Но самое главное, что нас сейчас интересует, — это стандартный поток ввода, вывода и поток ошибок. Это файловые дескрипторы 0, 1 и 2. Мы видим, что они равны терминалу.\n", + "\n", + "Давайте попробуем прервать выполнение программы при помощи `Ctrl+C` и перенаправим стандартный вывод в файл. Делается это при помощи такой команды в любой файл." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "af824409", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "^C\r\n", + "Traceback (most recent call last):\r\n", + " File \"ex1.py\", line 7, in \r\n", + " time.sleep(2)\r\n", + "KeyboardInterrupt\r\n" + ] + } + ], + "source": [ + "! python ex1.py > log.txt" + ] + }, + { + "cell_type": "markdown", + "id": "0861b697", + "metadata": {}, + "source": [ + "Снова поищем наш процесс при помощи команды ps. Его pid = 19347." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "444d66fc", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\n", + "mikhayl+ 19347 0.0 0.0 125028 5700 pts/6 S+ 13:38 0:00 python ex1.py\n", + "mikhayl+ 19366 0.0 0.0 113128 1300 pts/8 Ss+ 13:40 0:00 /bin/bash -c ps aux| head -1 ; ps aux | grep ex1.py\n", + "mikhayl+ 19370 0.0 0.0 112660 924 pts/8 S+ 13:40 0:00 grep ex1.py\n" + ] + } + ], + "source": [ + "! ps aux| head -1 ; ps aux | grep ex1.py" + ] + }, + { + "cell_type": "markdown", + "id": "06385847", + "metadata": {}, + "source": [ + "И при помощи `lsof` выведем список открытых файлов." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "9282bc58", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\r\n", + "python 19347 mikhaylovaf cwd DIR 253,2 128 1611044178 /home/mikhaylovaf/projects/python/5. Многопоточное и асинхронное программирование/1. Процессы и потоки\r\n", + "python 19347 mikhaylovaf rtd DIR 253,0 262 64 /\r\n", + "python 19347 mikhaylovaf txt REG 253,0 11328 610823 /usr/bin/python3.6\r\n", + "python 19347 mikhaylovaf mem REG 253,0 106070960 2704 /usr/lib/locale/locale-archive\r\n", + "python 19347 mikhaylovaf mem REG 253,0 2127336 33577165 /usr/lib64/libc-2.17.so\r\n", + "python 19347 mikhaylovaf mem REG 253,0 1139680 33577173 /usr/lib64/libm-2.17.so\r\n", + "python 19347 mikhaylovaf mem REG 253,0 14872 33577199 /usr/lib64/libutil-2.17.so\r\n", + "python 19347 mikhaylovaf mem REG 253,0 19776 33577171 /usr/lib64/libdl-2.17.so\r\n", + "python 19347 mikhaylovaf mem REG 253,0 144792 33577191 /usr/lib64/libpthread-2.17.so\r\n", + "python 19347 mikhaylovaf mem REG 253,0 3144192 34017354 /usr/lib64/libpython3.6m.so.1.0\r\n", + "python 19347 mikhaylovaf mem REG 253,0 164112 33577158 /usr/lib64/ld-2.17.so\r\n", + "python 19347 mikhaylovaf mem REG 253,0 26254 33577463 /usr/lib64/gconv/gconv-modules.cache\r\n", + "python 19347 mikhaylovaf 0u CHR 136,6 0t0 9 /dev/pts/6\r\n", + "python 19347 mikhaylovaf 1w REG 253,2 0 1611044181 /home/mikhaylovaf/projects/python/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/log.txt\r\n", + "python 19347 mikhaylovaf 2u CHR 136,6 0t0 9 /dev/pts/6\r\n" + ] + } + ], + "source": [ + "! lsof -p 19347" + ] + }, + { + "cell_type": "markdown", + "id": "8966c1e8", + "metadata": {}, + "source": [ + "Результат команды `lsof` доказывает нам, что стандартный вывод у нашего процесса поменялся на файл.\n", + "\n", + "Итак, на этой лекции мы посмотрели и узнали, что такое процесс, как выглядит процесс в нашей операционной системе. Мы узнали о том, как работает процесс, что он делает системные вызовы. Также изучили характеристики запущенного `python` процесса и узнали, как работают команды `top`, `ps`, `lsof` и `strace`.\n", + "\n", + "На следующей лекции мы продолжим изучать процессы и узнаем, как создавать дочерние процессы на Python." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Синхронизация потоков.ipynb b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Синхронизация потоков.ipynb new file mode 100644 index 0000000..ce14c72 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Синхронизация потоков.ipynb @@ -0,0 +1,334 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "79d36a4a", + "metadata": {}, + "source": [ + "# Синхронизация потоков #" + ] + }, + { + "cell_type": "markdown", + "id": "9d01200a", + "metadata": {}, + "source": [ + "На этой лекции мы поговорим про синхронизацию потоков и обсудим очереди, блокировки и условные переменные. Если вы запустите несколько потоков для решения своей задачи, то вам рано или поздно придётся обмениваться данными между потоками. Для этого как раз понадобятся все эти вещи, которые мы обсудим.\n", + "\n", + "Давайте для начала разберёмся, как можно использовать модуль `queue` и очереди для обмена данными между потоками." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21d47d71", + "metadata": {}, + "outputs": [], + "source": [ + "# Очереди, модуль queue\n", + "from queue import Queue\n", + "from threading import Thread\n", + "\n", + "def worker(q, n):\n", + " while True:\n", + " item = q.get()\n", + " if item is None:\n", + " break\n", + " \n", + " print(f\"process data: {n} {item}\")\n", + " \n", + "q = Queue(5)\n", + "th1 = Thread(target=worker, args=(q, 1))\n", + "th2 = Thread(target=worker, args=(q, 2))\n", + "th1.start(); th2.start()\n", + "\n", + "for i in range(50):\n", + " q.put(i)\n", + " \n", + "q.put(None); q.put(None)\n", + "th1.join(); th2.join()" + ] + }, + { + "cell_type": "markdown", + "id": "4278c862", + "metadata": {}, + "source": [ + "Использование очередей выглядит достаточно простым. На примере мы создаём объект типа очередь с максимальным размером 5. Для помещения элементов в очередь необходимо использовать метод `put` для объекта `queue`. Хочу обратить ваше внимание, что, если в очереди будет уже пять элементов, то вызов метода `put` заблокирует выполнение потока, который вызвал этот метод, и будет ждать, пока не появится в очереди свободное место.\n", + "\n", + "Итак, для обработки сообщений этой очереди мы создаём пару потоков — это объекты класса `Thread`, мы обсуждали это уже на предыдущей лекции, как создавать потоки. Передаём в этот объект функцию `worker`, и этой функции мы передаём нашу очередь. \n", + "\n", + "Итак, наша функция `worker` будет выполняться в двух потоках независимых. Они будут выполняться параллельно. Каждый поток в бесконечном цикле будет получать сообщение из очереди при помощи вызова метода `get` у объекта `q`.\n", + "\n", + "Давайте выполним этот код в консоли и посмотрим, как он выполняется. Я подготовил заранее код. Запускаем его при помощи интерпретатора Python 3." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "74d6f330", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "process data: 1 0\n", + "process data: 2 1\n", + "process data: 1 2\n", + "process data: 1 3\n", + "process data: 2 4\n", + "process data: 1 5\n", + "process data: 2 6\n", + "process data: 2 7\n", + "process data: 1 8\n", + "process data: 2 9\n", + "process data: 2 10\n", + "process data: 1 11\n", + "process data: 2 12\n", + "process data: 2 13\n", + "process data: 1 14\n", + "process data: 2 15\n", + "process data: 2 16\n", + "process data: 1 17\n", + "process data: 2 18\n", + "process data: 2 19\n", + "process data: 1 20\n", + "process data: 2 21\n", + "process data: 2 22\n", + "process data: 1 23\n", + "process data: 2 24\n", + "process data: 2 25\n", + "process data: 1 26\n", + "process data: 2 27\n", + "process data: 2 28\n", + "process data: 1 29\n", + "process data: 2 30\n", + "process data: 2 31\n", + "process data: 1 32\n", + "process data: 2 33\n", + "process data: 2 34\n", + "process data: 1 35\n", + "process data: 2 36\n", + "process data: 2 37\n", + "process data: 1 38\n", + "process data: 2 39\n", + "process data: 2 40\n", + "process data: 1 41\n", + "process data: 2 42\n", + "process data: 2 43\n", + "process data: 1 44\n", + "process data: 2 45\n", + "process data: 2 46\n", + "process data: 1 47\n", + "process data: 2 48\n", + "process data: 2 49\n" + ] + } + ], + "source": [ + "! python ex_queue.py" + ] + }, + { + "cell_type": "markdown", + "id": "8508171d", + "metadata": {}, + "source": [ + "Хочу обратить ваше внимание, что, действительно, наши потоки выполнили этот код параллельно, и мы видим то цифру 2, то цифру 1 в нашем стандартном выводе. Большое внимание нужно уделить правильному завершению потока. С точки зрения процесса, ресурсами владеет процесс, то есть выделенная память или открытый файл — ими владеет процесс. Но процесс ничего не знает о том, что делает с этими ресурсами поток. И если поток завершить аварийно, то файл может остаться незакрытым, блокировка может остаться невысвобожденной, и теоретически это может привести к непредвиденным последствиям. Поэтому в Python не существует функции аварийного завершения потока. Очень важно делать это правильно в функции самого потока. На приведённом примере в очередь помещается специальное значение `None`, и функция потока при проверке условия завершает свою работу. Код с использованием очередей выглядит достаточно просто, и предпочтительнее использовать очереди при разработке многопоточных программ.\n", + "\n", + "Тем не менее, иногда приходится использовать блокировки. Блокировки как минимум замедляют работу программы. Тем не менее, иногда их нужно применять. Давайте рассмотрим пример, который я привёл на слайде." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85c77ff3", + "metadata": {}, + "outputs": [], + "source": [ + "# Синхронизация потоков, race condition\n", + "import threading\n", + "\n", + "class Point(object):\n", + " def __init__(self, x, y):\n", + " self.set(x, y)\n", + " \n", + " def get(self):\n", + " return (self.x, self.y)\n", + " \n", + " def set(self, x, y):\n", + " self.x = x\n", + " self.y = y\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "492e4ec8", + "metadata": {}, + "source": [ + "Предположим, у нас есть класс Точка, и у класса Точка есть координаты `x` и `y`. Также у этого класса есть метод `get`, который возвращает эти координаты, и метод `set`, который задаёт новые координаты." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fea1d1d9", + "metadata": {}, + "outputs": [], + "source": [ + "# use in threads\n", + "my_point = Point(10, 20)\n", + "my_point.set(15, 10)\n", + "my_point.get()" + ] + }, + { + "cell_type": "markdown", + "id": "46e2e5b2", + "metadata": {}, + "source": [ + "Предположим, что мы создали объект класса Точка и используем этот объект в большом количестве потоков. Эти потоки, некоторые вызывают метод `get`, некоторые вызывают метод `set`. Если бы не было блокировок, то может возникнуть такая ситуация, когда один поток изменил значение переменной `x` или координаты `x`, а другой поток в это время вернул координаты `x` и `y`. Мы получили неконсистентное состояние объекта, когда у него частично одна координата изменена, а вторая нет. Для того чтобы избежать подобных ситуаций, и нужны блокировки. Для того чтобы создать объект блокировки необходимо вызвать вот такую конструкцию." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba350d3c", + "metadata": {}, + "outputs": [], + "source": [ + "# Синхронизация потоков, блокировки\n", + "import threading\n", + "\n", + "class Point(object):\n", + " def __init__(self, x, y):\n", + " self.mutex = threading.RLock()\n", + " self.set(x, y)\n", + " \n", + " def get(self):\n", + " with self.mutex:\n", + " return (self.x, self.y)\n", + " \n", + " def set(self, x, y):\n", + " with self.mutex:\n", + " self.x = x\n", + " self.y = y" + ] + }, + { + "cell_type": "markdown", + "id": "937fbcc5", + "metadata": {}, + "source": [ + "Создаём объект блокировки, и при помощи контекстного менеджера мы захватываем блокировку, а при выходе из контекстного менеджера блокировка высвобождается. Таким образом, легко и удобно создавать блокировки на Python. Подобные ситуации иногда называют гонкой за ресурсами, или `race condition`.\n", + "\n", + "Давайте рассмотрим ещё один вариант применения блокировок. Их можно использовать без контекстного менеджера. Выглядит это тоже довольно-таки просто." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d55ca1f", + "metadata": {}, + "outputs": [], + "source": [ + "# Синхронизация потоков, блокировки\n", + "import threading\n", + "\n", + "a = threading.RLock()\n", + "b = threading.RLock()\n", + "\n", + "def foo():\n", + " try:\n", + " a.acquire()\n", + " b.acquire()\n", + " finally:\n", + " a.release()\n", + " b.release()" + ] + }, + { + "cell_type": "markdown", + "id": "72d1888f", + "metadata": {}, + "source": [ + "Мы создаём объекты класса `RLock` и затем вызываем методы `acquire` — это получить или захватить блокировку и метод `release` для того, чтобы высвободить её.\n", + "\n", + "Если мы запустим подобный код в большом количестве процессов, то рано или поздно это приведёт к ситуации, которая называется `deadlock`. Дело в том, что мы освобождаем в неправильной последовательности блокировки. Нужно учитывать это в своих программах и отдавать предпочтение использованию контекстного менеджера при работе с блокировками.\n", + "\n", + "Также в Python существует ещё и объект класса `Lock`, а не `RLock`, но предпочтительнее использовать объекты `RLock`. Они позволяют в одном потоке получить блокировку дважды.\n", + "\n", + "В Python существует ещё один механизм для синхронизации потоков. Он называется \"условные переменные\". Давайте рассмотрим класс Очередь." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cc49d357", + "metadata": {}, + "outputs": [], + "source": [ + "# Синхронизация потоков, условные переменные\n", + "class Queue(object):\n", + " def __init__(self, size=5):\n", + " self._size = size\n", + " self._queue = []\n", + " self._mutex = threading.RLock()\n", + " self._empty = threading.Condition(self._mutex)\n", + " self._full = threading.Condition(self._mutex)\n", + " \n", + " def put(self, val):\n", + " with self._full:\n", + " while len(self._queue) >= self._size:\n", + " self._full.wait()\n", + " \n", + " self._queue.append(val)\n", + " self._empty.notify()\n", + " \n", + " def get(self):\n", + " with self._empty:\n", + " while len(self._queue) == 0:\n", + " self._empty.wait()\n", + "\n", + " ret = self._queue.pop(0)\n", + " self._full.notify()\n", + " return ret" + ] + }, + { + "cell_type": "markdown", + "id": "df62337b", + "metadata": {}, + "source": [ + "Это очередь, с которой нужно будет работать в большом количестве потоков. У неё есть операции `put` и `get`, и, конечно же, у неё есть какой-то размер. Очередь не должна расти больше заданного размера. Если мы выполним операцию `put`, а в очереди уже достаточно большое количество элементов, то нам необходимо ждать пока это количество уменьшится. Вопрос — сколько ждать? Неизвестно. Ответа на этот вопрос мы не получим.\n", + "\n", + "Для решения подобной задачи можно использовать условные переменные. Условные переменные в конструктор получает объект блокировки. Он есть по умолчанию, но если у нас эти переменные взаимозависимые, то необходимо использовать общую блокировку. И при помощи этих условных переменных очень легко и удобно ожидать событий при помощи вызова `wait` и оповещать все потоки, которые сейчас ждут наступления этого события. Таким образом, очень легко и удобно можно реализовать очередь в Python, которая работает в многопоточной программе.\n", + "\n", + "Итак, мы обсудили вопрос синхронизации потоков. Потоки выполняются в рамках одного процесса, в котором они были созданы. Потоки разделяют память и все ресурсы процесса. Потоки более легковесны по сравнению с процессами. Для обмена данными между потоками можно использовать очереди. Мы рассмотрели пример программы. Использование очередей предпочтительнее по сравнению с использованием блокировок. Также все потоки в Python исполняются с глобальной блокировкой интерпретатора, и об этом мы поговорим в следующей лекции." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Создание потоков.ipynb b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Создание потоков.ipynb new file mode 100644 index 0000000..c7bcc9f --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Создание потоков.ipynb @@ -0,0 +1,222 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3f098f8d", + "metadata": {}, + "source": [ + "# Создание потоков #" + ] + }, + { + "cell_type": "markdown", + "id": "a3903d26", + "metadata": {}, + "source": [ + "Итак, на прошлой лекции мы обсудили процессы. На этой лекции мы поговорим о потоках. Мы обсудим создание потоков при помощи модуля `threading` и использование класса `ThreadPoolExecutor`. С прикладной точки зрения поток целиком и полностью напоминает процесс. Он имеет свою последовательность инструкций для исполнения, у каждого потока есть свой собственный стек, но все потоки выполняются в рамках одного процесса. Этим они отличаются.\n", + "\n", + "Если мы говорили о процессах и у каждого процесса были свои ресурсы и память, то все созданные потоки разделяют память процесса и все его ресурсы. Управлением и выполнением потоков занимается операционная система. Но в Python есть свои ограничения для потоков. Их мы обсудим отдельно. Итак, давайте рассмотрим пример, как создать поток на Python." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc38c882", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание потока\n", + "from threading import Thread\n", + "\n", + "def f(name):\n", + " print(f\"hello {name}\")\n", + " \n", + "th = Thread(target=f, args=(\"Bob\",))\n", + "th.start()\n", + "th.join()" + ] + }, + { + "cell_type": "markdown", + "id": "d0b9bf31", + "metadata": {}, + "source": [ + "Всё делается очень просто и похоже на создание процессов. Используем модуль `threading`, импортируем класс `Thread`. Далее мы объявляем функцию, которую хотим исполнить в отдельном потоке функции `f`, которая просто выводит `hello` и аргумент, который ей передали. Далее мы создаем объект класса `Thread`, передаем в него нашу функцию `f` в качестве параметров и аргументы, с которыми эта функция должна будет быть вызвана. Опять же, после того как мы создали объект, никакого потока запущено не будет. Поток будет запущен, после того как мы вызовем метод `start` у этого объекта. Также очень важно дожидаться выполнения завершения всех созданных потоков при помощи метода `join` у этого объекта. Если мы выполним пример в консоли с этого слайда, то получим вывод `hello Bob`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "945c9899", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello Bob\r\n" + ] + } + ], + "source": [ + "! python ex7.py" + ] + }, + { + "cell_type": "markdown", + "id": "c1d72475", + "metadata": {}, + "source": [ + "Существует также альтернативный метод создания потока при помощи наследования." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98d9c32c", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание потока\n", + "from threading import Thread\n", + "\n", + "class PrintThread(Thread):\n", + " def __init__(self, name):\n", + " super().__init__()\n", + " self.name = name\n", + " \n", + " def run(self):\n", + " print(f\"hello {self.name}\")\n", + " \n", + "th = PrintThread(\"Mike\")\n", + "th.start()\n", + "th.join()" + ] + }, + { + "cell_type": "markdown", + "id": "5d3931d3", + "metadata": {}, + "source": [ + "Опять же, всё очень похоже на использование модуля `multiprocessing`, объявляем свой класс, наследуемся от класса `Thread`, в конструктор передаем нужные аргументы, для того чтобы выполнить нашу функцию в отдельном потоке. Запоминаем их в `self`. Далее мы переопределяем метод `run`, и метод `run` уже будет выполнен в отдельном потоке управления. В этом методе run мы можем использовать переданные аргументы конструктору, которые мы запомнили в `self`. Далее создаем объект класса `PrintThread`, передаем туда нужные аргументы, в качестве примера я передал строку `Mike`. Далее вызываем метод `start`. Здесь всё то же самое. Метод `start` создаст поток, и при помощи `join` мы будем ждать, пока этот поток завершится в основном потоке. Если мы исполним этот пример, то увидим вывод `hello Mike`. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9cf33666", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello Mike\r\n" + ] + } + ], + "source": [ + "! python ex8.py " + ] + }, + { + "cell_type": "markdown", + "id": "34ff1df1", + "metadata": {}, + "source": [ + "Итак, вы можете использовать любой из приведенных способов, всё зависит от ваших предпочтений, любите ли вы наследование либо просто любите передавать функции как аргументы.\n", + "\n", + "В Python3 появился очень удобный класс для создания пула потоков. Называется он `ThreadPoolExecutor` модуль `concurrent.futures`. Предположим, у нас есть некий массив чисел, и нам нужно ограничить количество потоков и рассчитать квадраты чисел для этого массива." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58346784", + "metadata": {}, + "outputs": [], + "source": [ + "# Пул потоков, concurrent.futures.Future\n", + "from concurrent.futures import ThreadPoolExecutor, as_completed\n", + "\n", + "def f(a):\n", + " return a * a\n", + "\n", + "# .shutdown() in exit\n", + "with ThreadPoolExecutor(max_workers=3) as pool:\n", + " results = [pool.submit(f, i) for i in range(10)]\n", + " \n", + " for future in as_completed(results):\n", + " print(future.result())" + ] + }, + { + "cell_type": "markdown", + "id": "7c473795", + "metadata": {}, + "source": [ + "Для этого можно использовать контекстный менеджер, указать в нем вызов `ThreadPoolExecutor` с параметром `max_workers`, который как раз отвечает за максимальное количество потоков, которые будут созданы в этом блоке `with`. Не нужно заботиться о завершении потоков. Нужное количество потоков будет создано автоматически, и при завершении контекстного менеджера будет вызвана функция `shutdown`, которая дождется завершения всех созданных потоков. Основная функция у этого `ThreadPoolExecutor` — это метод `submit`. Метод `submit` создает объект класса `concurrent.futures` `Future` — это такой объект, который еще не завершился, но выполняется и будет завершен в будущем. При помощи удобного метода `as_completed` из этого модуля `concurrent.futures` мы можем дождаться завершения всех наших объектов и получить результаты по мере завершения всех этих потоков, созданных нашим `Executor`. Если мы запустим пример, мы опять получим вывод, похожий на слайд." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a3ff48d3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\r\n", + "4\r\n", + "0\r\n", + "9\r\n", + "16\r\n", + "25\r\n", + "36\r\n", + "49\r\n", + "64\r\n", + "81\r\n" + ] + } + ], + "source": [ + "! python ex9.py" + ] + }, + { + "cell_type": "markdown", + "id": "4f9461cd", + "metadata": {}, + "source": [ + "Итак, на этой лекции мы обсудили, что такое поток. Мы рассмотрели особенности создания потоков при помощи модуля `threading`. Ключевое отличие потоков от процессов состоит в том, что они разделяют память и все ресурсы процессов, в рамках которого они запущены. Также мы рассмотрели работу класса `ThreadPoolExecutor` и обсудили, как можно ограничить количество потоков для решения конкретной задачи. \n", + "\n", + "На следующей лекции мы рассмотрим вопрос синхронизации потоков и блокировки." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Создание процессов.ipynb b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Создание процессов.ipynb new file mode 100644 index 0000000..01faeec --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Создание процессов.ipynb @@ -0,0 +1,482 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dfbf0c9c", + "metadata": {}, + "source": [ + "# Создание процессов #" + ] + }, + { + "cell_type": "markdown", + "id": "d3a89d3c", + "metadata": {}, + "source": [ + "На этой лекции мы поговорим про создание процессов именно на Python. Мы узнаем, как создать дочерний процесс, поговорим о том, как работает системный вызов `fork`, а также рассмотрим примеры создания процессов при помощи модуля `multiprocessing`.\n", + "\n", + "Процесс в операционной системе создается при помощи системного вызова `fork`. Давайте рассмотрим программу, которая создает дочерний процесс при помощи системного вызова `fork`. Я ее привел на слайде." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8d07b01", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание процесса на Python\n", + "import time\n", + "import os\n", + "\n", + "pid = os.fork()\n", + "\n", + "if pid == 0:\n", + " # дочерний процесс\n", + " while True:\n", + " print(f\"child: {os.getpid()}\" )\n", + " time.sleep(5)\n", + " \n", + "else:\n", + " # родительский процесс\n", + " print(f\"parent: {os.getpid()}\")\n", + " os.wait()" + ] + }, + { + "cell_type": "markdown", + "id": "4bfa662c", + "metadata": {}, + "source": [ + "Импортируем пару модулей `time` и `os`, затем вызываем системный вызов `fork`. Системный вызов `fork` создает точную копию родительского процесса. Это означает, что вся память, все файловые дискрипторы и все ресурсы, которые были доступны в родительском процессе, будут целиком и полностью скопированы в дочернем процессе. То есть после того, как системный вызов `fork` отработал, с этого момента у нас два процесса в операционной системе. Единственное отличие заключается в том, что системный вызов `fork` в родительский процесс вернет `pid` дочернего процесса, а в дочернем процессе переменная `pid` будет равна нулю.\n", + "\n", + "Код, который я сейчас выделил, будет исполнен в дочернем процессе, а код, который находится за веткой `else`, будет исполнен в родительском процессе. Итак, в родительском процессе мы вызываем системный вызов `os.wait`, это еще один дополнительный системный вызов и он позволяет нам дожидаться завершения дочернего процесса созданного. А в дочернем процессе вна бесконечном цикле выводим `pid` нашего процесса, который создали, и спим пять секунд.\n", + "\n", + "Давайте попробуем выполнить этот код в консоли и поизучаем снова характеристики наших процессов созданных. Наш пример. Запускаем его." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1c7aebc9", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "parent: 19405\n", + "child: 19406\n", + "child: 19406\n", + "^C\n", + "Traceback (most recent call last):\n", + " File \"ex2.py\", line 15, in \n", + "Traceback (most recent call last):\n", + " File \"ex2.py\", line 11, in \n", + " os.wait()\n", + "KeyboardInterrupt\n", + " time.sleep(5)\n", + "KeyboardInterrupt\n" + ] + } + ], + "source": [ + "! python ex2.py" + ] + }, + { + "cell_type": "markdown", + "id": "b139ad9e", + "metadata": {}, + "source": [ + "Итак, создалось два процесса, родительский и дочерний. Давайте попробуем поискать их в операционной системе. Переключимся в другую консоль при помощи известной нам команды `ps uax`. Найдем наш процесс." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c17304c7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\n", + "mikhayl+ 19407 0.0 0.0 125020 5736 pts/8 S+ 13:53 0:00 python ex2.py\n", + "mikhayl+ 19408 0.0 0.0 125020 3632 pts/8 S+ 13:53 0:00 python ex2.py\n", + "mikhayl+ 19409 0.0 0.0 113128 1300 pts/9 Ss+ 13:56 0:00 /bin/bash -c ps aux | head -1; ps aux | grep ex2.py\n", + "mikhayl+ 19413 0.0 0.0 112660 920 pts/9 S+ 13:56 0:00 grep ex2.py\n" + ] + } + ], + "source": [ + "! ps aux | head -1; ps aux | grep ex2.py" + ] + }, + { + "cell_type": "markdown", + "id": "6c806b8c", + "metadata": {}, + "source": [ + "Итак, мы видим процесс c `pid`'ом 19407, 19408. Это и есть наши созданные процессы. Можно отобразить результаты команды `ps` в иерархическом виде, чтобы посмотреть, какой из процессов родительский, а какой дочерний. Делается это при помощи дополнительного флага `f`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a465721e", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\n", + "mikhayl+ 19414 0.0 0.0 113128 1304 pts/9 Ss+ 13:56 0:00 | | \\_ /bin/bash -c ps auxf | head -1; ps auxf | grep ex2.py\n", + "mikhayl+ 19418 0.0 0.0 112660 924 pts/9 S+ 13:56 0:00 | | \\_ grep ex2.py\n", + "mikhayl+ 19407 0.0 0.0 125020 5736 pts/8 S+ 13:53 0:00 | \\_ python ex2.py\n", + "mikhayl+ 19408 0.0 0.0 125020 3632 pts/8 S+ 13:53 0:00 | \\_ python ex2.py\n" + ] + } + ], + "source": [ + "! ps auxf | head -1; ps auxf | grep ex2.py" + ] + }, + { + "cell_type": "markdown", + "id": "d2b2c77e", + "metadata": {}, + "source": [ + "Действительно видим наш родительский процесс и дочерний. Давайте посмотрим, что процессы делают. Для этого нам понадобится команда `strace` и дополнительные права." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "66c5163f", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "strace: Process 19408 attached\n", + "select(0, NULL, NULL, NULL, {0, 365190}) = 0 (Timeout)\n", + "write(1, \"child: 19408\\n\", 13) = 13\n", + "select(0, NULL, NULL, NULL, {5, 0}^C\n", + "strace: Process 19408 detached\n", + " \n" + ] + } + ], + "source": [ + "! strace -p 19408" + ] + }, + { + "cell_type": "markdown", + "id": "207d0457", + "metadata": {}, + "source": [ + "Итак, мы видим, что наш созданный дочерний процесс делает системный вызов `write` и выводит информацию в стандартный поток вывода. Теперь узнаем, что делает родительский процесс." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f95d20b7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "strace: Process 19407 attached\n", + "wait4(-1, ^C\n", + "strace: Process 19407 detached\n", + " \n" + ] + } + ], + "source": [ + "! strace -p 19407" + ] + }, + { + "cell_type": "markdown", + "id": "25bd9db4", + "metadata": {}, + "source": [ + "Родительский процесс сделал системный вызов `wait`, и операционная система сама оповестит его о том, когда дочерний процесс завершится.\n", + "\n", + "Давайте остановимся немного еще раз на памяти в родительском и дочернем процессе и рассмотрим пример. Итак, у нас есть программа, мы объявили в ней переменную `foo`, присвоили ей значение \"bar\" и делаем системный вызов `fork`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7175183", + "metadata": {}, + "outputs": [], + "source": [ + "# Память родительского и дочернего процесса\n", + "import os\n", + "\n", + "foo = \"bar\"\n", + "\n", + "if os.fork() == 0:\n", + " # дочерний процесс\n", + " foo = \"baz\"\n", + " print(\"child:\", foo)\n", + " \n", + "else:\n", + " # родительский процесс\n", + " print(\"parent:\", foo)\n", + " os.wait()" + ] + }, + { + "cell_type": "markdown", + "id": "dbec21e8", + "metadata": {}, + "source": [ + "Вот этот код мы исполняем в дочернем процессе. После того, как отработал системный вызов `fork`, как я уже говорил, вся память целиком и полностью будет скопирована из родительского процесса в дочерний. То есть у нас переменная `foo` будет точно так же доступна в дочернем процессе. И у нее будет значение \"bar\", так как память была скопирована. Но если мы изменим значение `foo` в дочернем процессе, это никак не повлияет на родительский процесс, на переменную `foo`, которая была объявлена в родительском процессе. Если мы выведем в родительском процессе ее значение, то оно будет равно значению \"bar\"." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fef004b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "parent: bar\r\n", + "child: baz\r\n" + ] + } + ], + "source": [ + "! python ex3.py" + ] + }, + { + "cell_type": "markdown", + "id": "280c49e6", + "metadata": {}, + "source": [ + "Итак, память целиком и полностью копируется. Если ее рассматривать, то это разные памяти. У дочернего процесса своя, у родительского процесса своя. То же самое относится и к файловым дискрипторам.\n", + "\n", + "Рассмотрим пример, который я привел на слайде." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36018536", + "metadata": {}, + "outputs": [], + "source": [ + "# Файлы в родительском и дочернем процессе\n", + "# $ cat data.txt\n", + "# example string1\n", + "# example string2\n", + "\n", + "import os\n", + "f = open(\"data.txt\")\n", + "foo = f.readline()\n", + "\n", + "if os.fork() == 0:\n", + " # дочерний процесс\n", + " foo = f.readline()\n", + " print(f\"child: {foo}\")\n", + " \n", + "else:\n", + " # родительский процесс\n", + " foo = f.readline()\n", + " print(f\"parent: {foo}\")" + ] + }, + { + "cell_type": "markdown", + "id": "9cbbb1a0", + "metadata": {}, + "source": [ + "Предположим, у нас есть небольшой файл. Я подготовил такой файл с двумя строчками `example string1` и `example string2`. Тоже так же импортируем системный модуль `os`. Открываем файл на чтение и читаем в переменную `foo` одну строчку. После того, как мы считали одну строчку, делаем системный вызов `fork`. После этого у нас создается точная копия родительского процесса и все файловые дискрипторы, вся память опять Если мы в дочернем процессе снова вызовем метод `readline` у файла или у объекта `f`, то мы прочитаем уже вторую строчку из этого файла. И снова это никак не повлияет на родительский процесс. В родительском процессе, если мы вызовем `readline`, то мы точно так же считаем вторую строчку.\n", + "\n", + "Итак, еще раз обращаю внимание, что не только память, но и файловые дискрипторы целиком и полностью копируются в дочернем процессе, когда мы делаем системный вызов `fork`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "5407785f", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "parent: example string2\r\n", + "child: example string2\r\n" + ] + } + ], + "source": [ + "! python ex4.py" + ] + }, + { + "cell_type": "markdown", + "id": "e4464c2d", + "metadata": {}, + "source": [ + "Все эти примеры носят обучающий характер, и обычно код с использованием системных вызовов `fork` немного сложнее. `fork` может вернуть ошибку, ее нужно проверять, и обычно в Python'е используют модуль `multiprocessing` для создания процессов. Рассмотрим пример." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d94def7", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание процесса, модуль multiprocessing\n", + "from multiprocessing import Process\n", + "\n", + "def f(name):\n", + " print(f\"hello {name}\")\n", + " \n", + "p = Process(target=f, args=(\"Bob\",))\n", + "p.start()\n", + "p.join()" + ] + }, + { + "cell_type": "markdown", + "id": "d3b5b21d", + "metadata": {}, + "source": [ + "Для того, чтобы запустить процесс на Python необходимо импортировать класс `Process` из модуля `multiprocessing`, создать объект класса `Process`, передать ему в конструктор функцию, которую мы хотим исполнить в отдельном дочернем процессе и аргументы этой функции. После того, как мы создали объект, никакого процесса создано не будет. Процесс будет создан тогда, когда мы вызовем метод `start` нашего объекта. Вот здесь, внутри метода `start`, будет вызван системный вызов `fork` и исполнена наша функция `f` в отдельном процессе. Очень важно ожидать завершения всех созданных дочерних процессов. Для этого можно воспользоваться удобной функцией `join` для нашего объекта `p`. Как мы видим, системные вызовы `fork` и `wait` спрятаны внутри красивых оберток. Вообще, не в каждой операционной системе есть системный вызов `fork` и поэтому в `multiprocessing` все аккуратно сделано за вас. И не надо за это беспокоиться. Просто используем модуль `multiprocessing` для создания собственных дочерних процессов. Если мы исполним этот пример, то в стандартном выводе мы увидим строчку `hello Bob`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d5a38c40", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello Bob\r\n" + ] + } + ], + "source": [ + "! python ex5.py" + ] + }, + { + "cell_type": "markdown", + "id": "b27125c7", + "metadata": {}, + "source": [ + "Существует также альтернативный метод создания процесса при помощи `multiprocessing` – при помощи наследования." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "222e55c3", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание процесса, модуль multiprocessing\n", + "from multiprocessing import Process\n", + "\n", + "class PrintProcess(Process):\n", + " def __init__(self, name):\n", + " super().__init__()\n", + " self.name = name\n", + "\n", + " def run(self):\n", + " print(f\"hello {self.name}\")\n", + " \n", + "p = PrintProcess(\"Mike\")\n", + "p.start()\n", + "p.join()" + ] + }, + { + "cell_type": "markdown", + "id": "48c2df7d", + "metadata": {}, + "source": [ + "Для этого мы объявляем свой класс, наследуемся от класса `multiprocessing Process`. В конструктор передаем нужные параметры для функции, которая должна быть запущена в дочернем процессе. И переопределяем метод `run`. В методе `run` мы реализуем код, вызываем функции дополнительные и используем все параметры. Дальше все стандартно. Создаем объект нашего класса `PrintProcess`, передаем туда параметры, вызываем метод `start`. Метод `start` вызовет `fork` и выполнит наш код в дочернем процессе. Для завершения дочернего процесса мы вызываем метод `join`. Если мы выполним этот код, то увидим вывод `hello Mike`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1be7cfff", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello Mike\r\n" + ] + } + ], + "source": [ + "! python ex6.py" + ] + }, + { + "cell_type": "markdown", + "id": "b7a2cbd1", + "metadata": {}, + "source": [ + "Очень важно ожидать завершения всех дочерних процессов, чтобы контролировать освобождение всех ресурсов.\n", + "\n", + "Итак, на этой лекции мы поговорили о том, как создать дочерний процесс, обсудили детали работы системного вызова `fork`, а также обсудили, как создать процесс при помощи удобных методов из модуля `multiprocessing`. Мы не говорили о вопросах обмена данными между процессами и синхронизации процессов. Мы рассмотрим эти вопросы на примере потоков." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Тест по процессам и потокам (Clear).ipynb b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Тест по процессам и потокам (Clear).ipynb new file mode 100644 index 0000000..3108afc --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Тест по процессам и потокам (Clear).ipynb @@ -0,0 +1,115 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4020485b", + "metadata": {}, + "source": [ + "# Тест по процессам и потокам #" + ] + }, + { + "cell_type": "markdown", + "id": "5e1cf0b3", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "1e130053", + "metadata": {}, + "source": [ + "##### 1. Все процессы работают в ОС\n", + "\n", + "- [ ] последовательно, ОС занимается переключением контекста\n", + "- [ ] параллельно, одновременно могут выполняться несколько процессов на одном ядре CPU" + ] + }, + { + "cell_type": "markdown", + "id": "7952a2d2", + "metadata": {}, + "source": [ + "##### 2. Предположим вывод команды `lsof` для процесса с `pid 1701`:\n", + "\n", + "```\n", + "lsof -p 1701\n", + "...\n", + "python 1701 user mem REG 8,1 147688 781852 /lib/i386-linux-gnu/ld-2.23.so\n", + "python 1701 user 0u CHR 136,9 0t0 12 /dev/pts/9\n", + "python 1701 user 1w REG 8,1 0 545637 /home/user/log.txt\n", + "python 1701 user 2u CHR 136,9 0t0 12 /dev/pts/9\n", + "```\n", + "\n", + "Стандартный вывод у процесса с `pid 1701` – это\n", + "\n", + "- [ ] терминал\n", + "- [ ] область памяти в библиотеке `ld-2.23.so`\n", + "- [ ] файл `/home/user/log.txt`\n", + "- [ ] отсутствует" + ] + }, + { + "cell_type": "markdown", + "id": "693d6bbc", + "metadata": {}, + "source": [ + "##### 3. Выделить истинные выражения:\n", + "\n", + "- [ ] выполнением Python потоков в Linux занимается операционная система\n", + "- [ ] все потоки разделяют память процесса в котором они созданы\n", + "- [ ] выполнением Python потоков в Linux занимается процесс, в котором они запущены\n", + "- [ ] у каждого потока есть собственная память\n", + "- [ ] потоки в Python выполняются в рамках одного процесса, разделяя GIL" + ] + }, + { + "cell_type": "markdown", + "id": "591de8bd", + "metadata": {}, + "source": [ + "##### 4. Какие участки кода в Python могут выполняться без GIL?\n", + "\n", + "- [ ] Вызовы сторонних библиотек на языке C\n", + "- [ ] Функции, выполняющиеся в `ThreadPoolExecutor`\n", + "- [ ] Функции, занимающиеся вычислениями на CPU\n", + "- [ ] Системные вызовы" + ] + }, + { + "cell_type": "markdown", + "id": "43165d4c", + "metadata": {}, + "source": [ + "##### 5. Что происходит с памятью в дочернем процессе?\n", + "\n", + "- [ ] Разделяется с родительским процессом\n", + "- [ ] Копируется из родительского процесса" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Тест по процессам и потокам.ipynb b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Тест по процессам и потокам.ipynb new file mode 100644 index 0000000..e037b52 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/1. Процессы и потоки/Тест по процессам и потокам.ipynb @@ -0,0 +1,106 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4020485b", + "metadata": {}, + "source": [ + "# Тест по процессам и потокам #" + ] + }, + { + "cell_type": "markdown", + "id": "1e130053", + "metadata": {}, + "source": [ + "1. Все процессы работают в ОС\n", + "\n", + "- [x] последовательно, ОС занимается переключением контекста\n", + "- [ ] параллельно, одновременно могут выполняться несколько процессов на одном ядре CPU" + ] + }, + { + "cell_type": "markdown", + "id": "7952a2d2", + "metadata": {}, + "source": [ + "2. Предположим вывод команды `lsof` для процесса с `pid 1701`:\n", + "\n", + "```\n", + "lsof -p 1701\n", + "...\n", + "python 1701 user mem REG 8,1 147688 781852 /lib/i386-linux-gnu/ld-2.23.so\n", + "python 1701 user 0u CHR 136,9 0t0 12 /dev/pts/9\n", + "python 1701 user 1w REG 8,1 0 545637 /home/user/log.txt\n", + "python 1701 user 2u CHR 136,9 0t0 12 /dev/pts/9\n", + "```\n", + "\n", + "Стандартный вывод у процесса с `pid 1701` – это\n", + "\n", + "- [ ] терминал\n", + "- [ ] область памяти в библиотеке `ld-2.23.so`\n", + "- [x] файл `/home/user/log.txt`\n", + "- [ ] отсутствует" + ] + }, + { + "cell_type": "markdown", + "id": "693d6bbc", + "metadata": {}, + "source": [ + "3. Выделить истинные выражения:\n", + "\n", + "- [x] выполнением Python потоков в Linux занимается операционная система\n", + "- [x] все потоки разделяют память процесса в котором они созданы\n", + "- [ ] выполнением Python потоков в Linux занимается процесс, в котором они запущены\n", + "- [ ] у каждого потока есть собственная память\n", + "- [x] потоки в Python выполняются в рамках одного процесса, разделяя GIL" + ] + }, + { + "cell_type": "markdown", + "id": "591de8bd", + "metadata": {}, + "source": [ + "4. Какие участки кода в Python могут выполняться без GIL?\n", + "\n", + "- [x] Вызовы сторонних библиотек на языке C\n", + "- [ ] Функции, выполняющиеся в `ThreadPoolExecutor`\n", + "- [ ] Функции, занимающиеся вычислениями на CPU\n", + "- [x] Системные вызовы" + ] + }, + { + "cell_type": "markdown", + "id": "43165d4c", + "metadata": {}, + "source": [ + "5. Что происходит с памятью в дочернем процессе?\n", + "\n", + "- [ ] Разделяется с родительским процессом\n", + "- [x] Копируется из родительского процесса" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/client.py b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/client.py new file mode 100644 index 0000000..6bc2e2a --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/client.py @@ -0,0 +1,12 @@ +# создание сокета, клиент +import socket + +sock = socket.socket() +sock.connect(("127.0.0.1", 10001)) +sock.sendall("ping".encode("utf8")) +sock.close() + +# более короткая запись +sock = socket.create_connection(("127.0.0.1", 10001)) +sock.sendall("ping".encode("utf8")) +sock.close() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/ex1.py b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/ex1.py new file mode 100644 index 0000000..ed117d7 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/ex1.py @@ -0,0 +1,24 @@ +# создание сокета, таймауты и обработка ошибок +# сервер +import socket + +with socket.socket() as sock: + sock.bind(("", 10001)) + sock.listen() + + while True: + conn, addr = sock.accept() + conn.settimeout(5) # timeout := None|0|gt 0 + with conn: + while True: + try: + data = conn.recv(1024) + + except socket.timeout: + print("close connection by timeout") + break + + if not data: + break + + print(data.decode("utf8")) \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/mega_client.py b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/mega_client.py new file mode 100644 index 0000000..a249190 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/mega_client.py @@ -0,0 +1,8 @@ +import socket +import time + +for i in range(1000): + sock = socket.create_connection(("127.0.0.1", 10001)) + sock.sendall(f"Привет #{i}".encode("utf-8")) + time.sleep(1) + sock.close() diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/parallel_server.py b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/parallel_server.py new file mode 100644 index 0000000..b403980 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/parallel_server.py @@ -0,0 +1,39 @@ +# обработка нескольких соединений одновременно, процессы и потоки +import socket +import threading +import multiprocessing +import os + +def process_request(conn, addr): + print(f"connected client: {addr}") + with conn: + while True: + data = conn.recv(1024) + + if not data: + break + + print(data.decode("utf8")) + +def worker(sock): + while True: + conn, addr = sock.accept() + print(f"pid {os.getpid()}") + th = threading.Thread(target=process_request, args=(conn, addr)) + th.start() + +with socket.socket() as sock: + sock.bind(("", 10001)) + sock.listen() + + workers_count = 5 + workers_list = [ + multiprocessing.Process(target=worker, args=(sock,)) + for _ in range(workers_count) + ] + + for w in workers_list: + w.start() + + for w in workers_list: + w.join() diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/server.py b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/server.py new file mode 100644 index 0000000..a4232cc --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/server.py @@ -0,0 +1,21 @@ +# создание сокета, сервер +import socket + +# https://docs.python.org/3/library/socket.html +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.bind(("127.0.0.1", 10001)) # max port 65535 +sock.listen(socket.SOMAXCONN) + +conn, addr = sock.accept() + +while True: + data = conn.recv(1024) + + if not data: + break + + # process data + print(data.decode("utf8")) + +conn.close() +sock.close() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Документация.ipynb b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Документация.ipynb new file mode 100644 index 0000000..feaf96c --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Документация.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c7b41d29", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "3b6b4cc2", + "metadata": {}, + "source": [ + "[Сокеты в Python](https://docs.python.org/3.6/library/socket.html \"18.1. socket — Low-level networking interface\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Обработка нескольких соединений.ipynb b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Обработка нескольких соединений.ipynb new file mode 100644 index 0000000..1dbd496 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Обработка нескольких соединений.ipynb @@ -0,0 +1,237 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8b2dae71", + "metadata": {}, + "source": [ + "# Обработка нескольких соединений #" + ] + }, + { + "cell_type": "markdown", + "id": "112a8b70", + "metadata": {}, + "source": [ + "Итак, на прошлой лекции мы рассматривали простые программы типа Клиент-Сервер и пробовали организовать взаимодействие между двумя процессами. А что делать, если этих процессов будет несколько или не несколько, а очень много. Как раз на этой лекции мы рассмотрим примеры обработки большого количества соединений на стороне сервера. Давайте рассмотрим наш изначальный пример, который мы запускали на серверной стороне." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a94b02c6", + "metadata": {}, + "outputs": [], + "source": [ + "# обработка нескольких соединений одновременно\n", + "import socket\n", + "\n", + "with socket.socket() as sock:\n", + " sock.bind((\"\", 10001))\n", + " sock.listen()\n", + " while True:\n", + " conn, addr = sock.accept()\n", + " print(f\"connected client: {addr}\")\n", + " \n", + " # процесс или поток для обработки соединения\n", + " with conn:\n", + " while True:\n", + " data = conn.recv(1024)\n", + " \n", + " if not data:\n", + " break\n", + " \n", + " print(data.decode(\"utf8\"))" + ] + }, + { + "cell_type": "markdown", + "id": "ba69797e", + "metadata": {}, + "source": [ + "Итак, мы создаем объект `socket`, вызываем `bind` и `listen`, и затем принимаем новое соединение. Если мы приняли соединение и начинаем его обработку в том же самом потоке управления, мы не можем принимать новые соединения. Если у нас будет большое количество клиентов, то все остальные клиенты будут вынуждены ждать, пока мы закончим работу с первым соединением. Какие подходы существуют для решения данной задачи?\n", + "\n", + "Конечно, мы можем создать процесс или поток для обработки отдельного соединения и выполнить в этом процессе или потоке код по его обработке. Давайте представим, что мы будем создавать процессы для обработки нового соединения, то есть у нас есть сервер, предположим, у нас есть 10,000 клиентов. Чтобы более конкретно говорить о каких-то примерах, можем представить себе мобильные приложения, которые сейчас так популярны. Например, они будут нам отправлять какие-то статистические данные. Мобильных приложений очень много, все они запускаются, и, предположим, 10,000 соединений одновременно приходит на наш сервер и нам надо обработать все эти запросы. Если мы создадим 10,000 процессов, это будет иметь ряд своих минусов. Как минимум, это потребует очень больших ресурсов от нашей операционной системы. На каждый процесс нужна память, каждый процесс нужно, чтобы операционная система управляла всем этим большим количеством процессов. Иногда, если даже и сам запрос требует небольшого количества ресурсов, например, нам что-то нужно просто записать в лог-файл, создание процесса на обработку этого соединения будет гораздо дороже, чем обработка. Тем не менее, такой подход иногда используется, и, если у вас небольшое количество соединений, плюсом будет то, что вы можете использовать все ядра операционной системы и распределять обработку по всем ядрам на сервере. Если же мы рассмотрим поток в качестве обработки нового соединения, то, как мы помним, все потоки работают в Python на одном ядре, и они ограничены GIL. Рано или поздно мы упремся в то, что нам не хватает одного ядра, и наш сервер будет отвечать не за приемлемое время, не за то, которое мы хотели бы от него ожидать. Тем не менее, и на потоках, особенно если они требуют операции ввода-вывода, можно получить достаточно производительный сервер. Давайте рассмотрим пример одновременной обработки сетевых запросов при помощи потоков." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c754304", + "metadata": {}, + "outputs": [], + "source": [ + "# обработка нескольких соединений одновременно, потоки\n", + "import socket\n", + "import threading\n", + "\n", + "def process_request(conn, addr):\n", + " print(f\"connected client: {addr}\")\n", + " with conn:\n", + " while True:\n", + " data = conn.recv(1024)\n", + " \n", + " if not data:\n", + " break\n", + " \n", + " print(data.decode(\"utf8\"))\n", + "\n", + "\n", + "with socket.socket() as sock:\n", + " sock.bind((\"\", 10001))\n", + " sock.listen()\n", + " while True:\n", + " conn, addr = sock.accept()\n", + " \n", + " th = threading.Thread(target=process_request, args=(conn, addr))\n", + " th.start()" + ] + }, + { + "cell_type": "markdown", + "id": "2fd60033", + "metadata": {}, + "source": [ + "Итак, мы создаем `socket`, вызываем нами известные методы `bind` и `listen`. Затем в бесконечном цикле принимаем входящее соединение от клиента. Как только мы приняли это входящее соединение, мы должны создать поток. Делаем это мы при помощи модуля `threading`, создаем объект класса `thread`, передаём ему в качестве аргумента функцию и наше соединение, с которым будем дальше в этой функции работать. Запускаем поток, и в основном потоке мы продолжаем акцептить новые соединения, и, тем самым, мы обрабатываем уже существующее соединение и, обрабатывая, ждём новых. В данном случае у нас может быть создано большое количество потоков, в которых мы точно также обрабатываем это соединение. Как я уже говорил, если процесс обработки этих соединений будет заниматься вводом-выводом, то такой код будет достаточно производительным. Тем не менее, если будет недостаточно одного ядра операционной системы, можно этот процесс распараллелить. Давайте рассмотрим пример, когда можно использовать и потоки, и процессы одновременно." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4eaeb07", + "metadata": {}, + "outputs": [], + "source": [ + "# обработка нескольких соединений одновременно, процессы и потоки\n", + "import socket\n", + "\n", + "with socket.socket() as sock:\n", + " sock.bind((\"\", 10001))\n", + " sock.listen()\n", + " # создание нескольких процессов\n", + " \n", + " while True:\n", + " # accept распределится \"равномерно\" между процессами\n", + " conn, addr = sock.accept()\n", + " # поток для обработки соединения\n", + " with conn:\n", + " while True:\n", + " data = conn.recv(1024)\n", + " \n", + " if not data:\n", + " break\n", + " \n", + " print(data.decode(\"utf8\"))" + ] + }, + { + "cell_type": "markdown", + "id": "ef549693", + "metadata": {}, + "source": [ + "Итак, для того чтобы обрабатывать одно соединение в нескольких процессах, нам нужно выполнить небольшой трюк. Итак, создаем объект класса `socket` в контекстном менеджере, вызываем `bind` и метод `listen`. После того как мы вызвали метод `listen`, мы должны создать несколько процессов, сделать `fork`. `Fork` мы рассматривали на предыдущих лекциях. После того как мы сделаем вызов `fork`, все ресурсы родительского процесса будут целиком и полностью скопированы в дочерние процессы, тем самым в наших дочерних процессах будет тот же самый `socket`. Если мы в этом `socket`'е сделаем вызов `accept` и будем ждать нового соединения от клиента, то системный вызов `accept` распределит равномерно между всеми дочерними процессами новые входящие соединения, а уже дальше в этих дочерних процессах, когда мы поймали новые соединения, мы уже сможем создать поток и обработать новые соединения.\n", + "\n", + "Здесь есть небольшой нюанс. Если, опять же, мы создадим несколько процессов, которые все одновременно делают системный вызов `accept`, то по умолчанию все они будут спать, а операционная система не будет потреблять никаких ресурсов. Но если будет приходить новое входящее соединение, операционная система будет будить все наши процессы. В этом месте есть небольшой `overhead`, то есть если мы будем постоянно принимать новые соединения, наш код будет далать небольшой `overhead`, нужно это понимать. Вот так может выглядеть код нашего сервера на процессах." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a684fda3", + "metadata": {}, + "outputs": [], + "source": [ + "# обработка нескольких соединений одновременно, процессы и потоки\n", + "import socket\n", + "import threading\n", + "import multiprocessing\n", + "\n", + "with socket.socket() as sock:\n", + " sock.bind((\"\", 10001))\n", + " sock.listen()\n", + " \n", + " workers_count = 3\n", + " workers_list = [\n", + " multiprocessing.Process(target=worker, args=(sock,))\n", + " for _ in range(workers_count)\n", + " ]\n", + " \n", + " for w in workers_list:\n", + " w.start()\n", + " \n", + " for w in workers_list:\n", + " w.join()" + ] + }, + { + "cell_type": "markdown", + "id": "8c2a535a", + "metadata": {}, + "source": [ + "Итак, как я уже говорил, мы создаем `socket`, вызываем методы `bind` и `listen`. Затем мы должны при помощи модуля `multiprocess` создать наши `worker`'ы, которые будут обрабатывать наши новые соединения. Итак, мы создаем наши `worker`'ы, запускаем их, и ждем, пока они завершатся. Давайте рассмотрим код наших `worker`'ов." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "321c0764", + "metadata": {}, + "outputs": [], + "source": [ + "# обработка нескольких соединений одновременно, процессы и потоки\n", + "\n", + "def process_request(conn, addr):\n", + " print(f\"connected client: {addr}\")\n", + " with conn:\n", + " while True:\n", + " data = conn.recv(1024)\n", + " \n", + " if not data:\n", + " break\n", + " \n", + " print(data.decode(\"utf8\"))\n", + " \n", + "def worker(sock):\n", + " while True:\n", + " conn, addr = sock.accept()\n", + " print(f\"pid {os.getpid()}\")\n", + " \n", + " th = threading.Thread(target=process_request, args=(conn, addr))\n", + " th.start()" + ] + }, + { + "cell_type": "markdown", + "id": "dc58479b", + "metadata": {}, + "source": [ + "Итак, каждый `worker`, который будет запущен в отдельном процессе, делает системный вызов `accept`. Все входящие соединения будут равномерно распределены между `worker`'ами при помощи операционной системы. И после того как соединение попало в наш процесс, необходимо создать поток. Создаем поток, передаем ему метод, в данном случае, `process_request`, и обрабатываем наше соединение.\n", + "\n", + "Таким образом, мы сможем решить проблему с `GIL`, то есть у нас будет уже несколько Python процессов запущенных, и мы сможем решить проблему с памятью, то есть все потоки, которые будут созданы в рамках одного процесса, они будут разделять его память, и переключение между потоками будет более легковесно в данном случае. Таким образом, мы сможем обработать достаточно большое количество входящих соединений.\n", + "\n", + "Итак, на этой лекции мы рассмотрели, как организовать взаимодействие между большим количеством клиентов и одним сервером. Мы рассмотрели несколько подходов, обсудили плюсы и минусы того или иного подхода, а также применили наши знания по процессам и потокам, и рассмотрели, как можно, используя процессы и потоки, получить эффективную обработку сетевых запросов." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Сокеты, клиент-сервер.ipynb b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Сокеты, клиент-сервер.ipynb new file mode 100644 index 0000000..c83d91d --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Сокеты, клиент-сервер.ipynb @@ -0,0 +1,270 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "68b87897", + "metadata": {}, + "source": [ + "# Сокеты, клиент-сервер #" + ] + }, + { + "cell_type": "markdown", + "id": "56b06ed6", + "metadata": {}, + "source": [ + "В предыдущих лекциях мы рассмотрели то, как устроены процессы и потоки в Python. В следующих лекциях мы будем изучать то, как устроены сокеты и как работают сетевые программы, и те знания, которые мы приобрели в предыдущих лекциях, понадобятся нам для организации взаимодействия по сети.\n", + "\n", + "Итак, давайте разберем, что такое сокеты, как они устроены и попробуем написать свою первую программу клиент-сервер, которая будет обмениваться данными между собой.\n", + "\n", + "Сокеты — это, прежде всего, кросс-платформенный механизм для обмена данными между отдельными процессами. Эти процессы могут работать на разных серверах, они могут быть написаны на разных языках, и, прежде всего, программа на Python, которая использует механизм сокетов, она осуществляет системные вызовы и взаимодействие с ядром операционной системы.\n", + "\n", + "Как правило, для организации сетевого взаимодействия нужен сервер, который изначально создает некое соединение и начинает «слушать» все запросы, которые поступают в него и программа-клиент, которая присоединяется к серверу и отправляет ему нужные данные. Давайте рассмотрим пример серверной программы." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fbc55f45", + "metadata": {}, + "outputs": [], + "source": [ + "# создание сокета, сервер\n", + "import socket\n", + "\n", + "# https://docs.python.org/3/library/socket.html\n", + "sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", + "sock.bind((\"127.0.0.1\", 10001)) # max port 65535\n", + "sock.listen(socket.SOMAXCONN)\n", + "\n", + "conn, addr = sock.accept()\n", + "\n", + "while True:\n", + " data = conn.recv(1024)\n", + " \n", + " if not data:\n", + " break\n", + " \n", + " # process data\n", + " print(data.decode(\"utf8\"))\n", + " \n", + "conn.close()\n", + "sock.close()" + ] + }, + { + "cell_type": "markdown", + "id": "9a9c078e", + "metadata": {}, + "source": [ + "Для того чтобы создать сокет, мы должны импортировать модуль `socket`. Далее мы должны создать объект типа `socket` из модуля `socket`. В него необходимо передать некоторые параметры. В данном случае это некоторое семейство — мы используем `address family`, мы используем конкретную константу `AF_INET` (`IPv4`), а также тип сокета. В данном примере мы используем потоковый сокет `SOCK_STREAM` (`TCP`).\n", + "\n", + "Полную информацию по типам сокетов, по типам `address family` можно посмотреть в документации на Python, либо в документации про то, как устроена сеть в операционной системе Linux." + ] + }, + { + "cell_type": "markdown", + "id": "b16a58fa", + "metadata": {}, + "source": [ + "Итак, мы создали объект `socket` — это потоковый сокет. Далее мы должны вызвать метод `bind`. В метод `bind` мы должны передать некую адресную пару — это `host`, в данном случае мы передаем `127.0.0.1`, и порт `127.0.0.1` будет означать, что наш сервер будет слушать все входящие соединения только локально на одной машине. Если мы укажем пустую строчку, либо адрес `0.0.0.0`, то наш сервер будет слушать входящие соединения со всех интерфейсов. Порт — это некая целочисленная константа, существуют некоторые зарезервированные порты, например, 80-й порт, обычно на нем работает HTTP-сервер, 43-й порт, 443-й порт. Как правило, порты с номерами до 2,000 являются системными, и мы должны использовать адреса больше значений 2,000, но максимальное значение для порта — это 65,535 (2 байта). Итак, системный вызов `bind` зарегистрировал нашу адресную пару в операционной системе. Двигаемся дальше.\n", + "\n", + "Далее, для того чтобы начать принимать соединения, мы должны вызвать метод `listen`. У метода `listen` есть необязательный параметр — это так называемый `backlog`, или размер очереди входящих соединений, которые еще не обработаны, для которых не был вызван метод `accept`. Если наш сервер будет не успевать принимать входящие соединения, то все эти соединения будут копиться в этой очереди, и если она превысит это максимальное значение, то операционная система выдаст ошибку `ConnectionRefused` для клиентской программы. Двигаемся дальше.\n", + "\n", + "Мы создали сокет, зарегистрировали адресную пару, вызвали метод `listen`. Далее мы должны вызвать метод `accept`, для того чтобы начать принимать входящее клиентское соединение. Системный вызов `accept` по умолчанию заблокируется, до тех пор, пока не появится клиентское соединение. Итак, если клиент вызовет метод `connect`, то наш метод `accept` вернет нам объект, который будет являться полнодуплексным каналом. У этого объекта будут доступны методы записи в этот канал и методы чтения. В нашем примере мы в бесконечном цикле будем вызывать чтение из нашего полнодуплексного канала. Если мы ничего не прочитали, это будет означать, что клиент закрыл соединение и нам необходимо тоже прекратить работу. В качестве обработки наших данных, которые мы прочитали с канала, мы просто выводим эти данные в консоль.\n", + "\n", + "После того как мы закончили работу с нашим клиентом, мы вызываем метод `close` для нашего объекта, который представляет собой полнодуплексный канал, и также закрываем сокет, который слушает новые соединения со стороны клиента. Давайте рассмотрим код на стороне клиента." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b93b286f", + "metadata": {}, + "outputs": [], + "source": [ + "# создание сокета, клиент\n", + "import socket\n", + "\n", + "sock = socket.socket()\n", + "sock.connect((\"127.0.0.1\", 10001))\n", + "sock.sendall(\"ping\".encode(\"utf8\"))\n", + "sock.close()\n", + "\n", + "# более короткая запись\n", + "sock = socket.create_connection((\"127.0.0.1\", 10001))\n", + "sock.sendall(\"ping\".encode(\"utf8\"))\n", + "sock.close()" + ] + }, + { + "cell_type": "markdown", + "id": "d3810db6", + "metadata": {}, + "source": [ + "Для того чтобы установить соединение с сервером, мы должны создать объект типа `socket.socket`. По умолчанию создается потоковый сокет с семейством `address family AF_INET`. После этого мы должны вызвать метод `connect`. `Connect` заблокируется до тех пор, пока сервер со своей стороны не вызовет метод `accept`. После того как системный вызов `connect` отработал, наш сокет готов к работе, и для него можно вызывать методы `send`, `sendall` или `recv`, для того чтобы получать данные с сервера. То есть, по сути, мы получили такой же полнодуплексный канал, с которым можно работать, отправлять и получать данные. После того как мы завершили работу с нашим клиентским сокетом, необходимо вызвать метод `close`.\n", + "\n", + "В Python существует более короткая запись для создания клиентского сокета — это вызов метода модуля `socket create_connection`. В `create_connection` мы передаем адресную пару, необязательный `timeout`. Про `timeout` мы еще с вами будем говорить в следующих лекциях. Этот вызов возвращает нам проключенное соединение, готовое для того, чтобы делать отправку или прием данных. \n", + "\n", + "Давайте попробуем запустить наш код и посмотрим, как он работает на самом деле. Нам потребуется код нашего сервера. Давайте запустим его при помощи команды python3." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "727da51b", + "metadata": {}, + "outputs": [], + "source": [ + "! python server.py" + ] + }, + { + "cell_type": "markdown", + "id": "953309cd", + "metadata": {}, + "source": [ + "Итак, наш сервер создал сокет и готов принимать новые соединения. Давайте попробуем присоединиться к нашему серверу. Импортируем модуль `socket`, создаем новое соединение при помощи `socket.create_connection`. Передаем адресную пару в виде хоста и порт — 10001. Итак, наш сокет готов со стороны клиента, для того чтобы отправлять и принимать данные. Давайте попробуем отправить данные. Хочу обратить ваше внимание, что при работе с данными по сети мы вынуждены отправлять именно байты, а не строки. Поэтому мы передаем байты. Пусть это будет строка `ping`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9321a4d3", + "metadata": {}, + "outputs": [], + "source": [ + "import socket\n", + "\n", + "sock = socket.create_connection((\"127.0.0.1\", 10001))\n", + "sock.sendall(\"ping\".encode(\"utf8\"))" + ] + }, + { + "cell_type": "markdown", + "id": "374ba711", + "metadata": {}, + "source": [ + "Давайте посмотрим на сервер — сервер получил эти данные и вывел нашу переданную строчку `ping`. Давайте попробуем закрыть соединение со стороны клиента. Для этого нам нужно вызвать метод `close`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1923afea", + "metadata": {}, + "outputs": [], + "source": [ + "sock.close()" + ] + }, + { + "cell_type": "markdown", + "id": "ac06e974", + "metadata": {}, + "source": [ + "Итак, мы закрыли соединение со стороны клиента. Наш сервер прекратил цикл `while` и закрыл соединение, и наша программа со стороны сервера завершила свою работу. Как я уже говорил, `socket` — это кроссплатформенный механизм и необязательно программа-клиент и сервер должны быть написаны на одном и том же языке.\n", + "\n", + "Давайте посмотрим, как наш пример будет работать, если мы воспользуемся вместо клиента программой `telnet`. Точно так же укажем адрес и порт, куда мы собираемся отправлять наши данные." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ddf2e8cb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trying 127.0.0.1...\n", + "Connected to 127.0.0.1.\n", + "Escape character is '^]'.\n", + "^C\n", + "Connection closed by foreign host.\n" + ] + } + ], + "source": [ + "! telnet 127.0.0.1 10001" + ] + }, + { + "cell_type": "markdown", + "id": "62f8277b", + "metadata": {}, + "source": [ + "Итак, `telnet` выполнил подключение, написал нам, что он приконнектился. Давайте попробуем отправить данные. Опять на стороне сервера мы видим, что данные получены Давайте попробуем закрыть соединение. Итак, мы видим, что наша серверная программа завершила свою работу. Ещё раз хочу обратить ваше внимание, что сокет — это кроссплатформенный механизм, который можно использовать на различных языках, в том числе и на Python. Хочется ещё обратить и остановить ваше внимание на некоторых ключевых моментах в нашей серверной и клиентской программе. В частности, это вызовы методов `close` для наших объектов, для соединения, на котором мы акцептим новые соединения клиентов, а также для объекта `connection`, который является полнодуплексным каналом. Наш код носит обучающий характер, и в нём нет обработки ошибок. Как правило, сетевые программы настоящие, они выглядят более сложно, в них, естественно обрабатываются ошибки, и одним из требований к сетевым программам является то, что необходимо правильно и грамотно завершать работу с созданными `connect`'ами и со всеми открытыми сокетами, тем самым контролируя ресурсы процесса, в котором всё это работает.\n", + "\n", + "В Python существует более удобный механизм для работы с сокетами в виде контекстных менеджеров. Давайте рассмотрим пример, в котором мы выполняем те же самые задачи, но используем контекстный менеджер." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78a642ef", + "metadata": {}, + "outputs": [], + "source": [ + "# создание сокета, контекстный менеджер\n", + "# сервер\n", + "import socket\n", + "\n", + "with socket.socket() as sock:\n", + " sock.bind((\"\", 10001))\n", + " sock.listen()\n", + " \n", + " while True:\n", + " conn, addr = sock.accept()\n", + " with conn:\n", + " while True:\n", + " data = conn.recv(1024)\n", + "\n", + " if not data:\n", + " break\n", + "\n", + " print(data.decode(\"utf8\"))\n", + "\n", + "# клиент\n", + "import socket\n", + "\n", + "with socket.create_connection((\"127.0.0.1\", 10001)) as sock:\n", + " sock.sendall(\"ping\".encode(\"utf8\"))" + ] + }, + { + "cell_type": "markdown", + "id": "913dadb2", + "metadata": {}, + "source": [ + "Итак, мы используем на стороне сервера конструкцию `with socket.socket`, создаём наш объект типа `socket`, вызываем методы `bind`, `listen`. Затем в бесконечном цикле вызываем `accept` и получаем новые соединения от клиентов. Для полученного объекта мы опять используем контекстный менеджер и мы не заботимся о вызовах метода `close`. После того как контекстный менеджер завершит свою работу, он автоматически вызовет метод `close` для нужных нам объектов. Это очень удобно, и это позволяет допускать вам меньшее количество ошибок при работе с сокетами. На стороне клиента мы используем снова контекстный менеджер для вызова `socket.create.connection` и опять не заботимся о вызове метода `close`. Предпочтительнее работать с контекстными менеджерами при написании клиент-серверных программ на языке Python.\n", + "\n", + "Итак, на этой лекции мы обсудили, что такое сокеты, как они устроены, мы попробовали написать свою первую сетевую программу типа клиент-сервер, попробовали поотправлять данные с клиента на сервер, и мы обсудили роль контекстных менеджеров в языке Python для написания сетевых программ. \n", + "\n", + "В следующей лекции мы продолжим с вами знакомство с сетевыми программами и углубимся в детали их работы." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Таймауты и обработка сетевых ошибок.ipynb b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Таймауты и обработка сетевых ошибок.ipynb new file mode 100644 index 0000000..66cc1a9 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Таймауты и обработка сетевых ошибок.ipynb @@ -0,0 +1,261 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5ad5c77d", + "metadata": {}, + "source": [ + "# Таймауты и обработка сетевых ошибок #" + ] + }, + { + "cell_type": "markdown", + "id": "ab4a8b58", + "metadata": {}, + "source": [ + "Итак, на этой лекции мы поговорим про обработку сетевых ошибок, а также обсудим таймауты для сокетов.\n", + "\n", + "Как правило, все обучающие примеры для работы с сетью выглядят очень просто. На самом деле, как дело доходит до настоящих программ, которые работают в нашей жизни, они все выглядят гораздо сложнее. Связано это прежде всего с обработкой ошибок. Как правило, сеть может работать стабильно не всегда. В ней могут появляться задержки, могут не доходить пакеты, а также могут быть разрывы соединений. Поэтому при написании ваших сетевых программ необходимо быть к этому готовыми. Необходимо работать с сокетами правильно. Прежде всего нужно задавать таймауты при сетевых операциях, а также очень грамотно обрабатывать сетевые ошибки. Давайте рассмотрим обучающий пример, в котором создается тот же самый знакомый нам код сервера." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46db3230", + "metadata": {}, + "outputs": [], + "source": [ + "# создание сокета, таймауты и обработка ошибок\n", + "# сервер\n", + "import socket\n", + "\n", + "with socket.socket() as sock:\n", + " sock.bind((\"\", 10001))\n", + " sock.listen()\n", + " \n", + " while True:\n", + " conn, addr = sock.accept()\n", + " conn.settimeout(5) # timeout := None|0|gt 0\n", + " with conn:\n", + " while True:\n", + " try:\n", + " data = conn.recv(1024)\n", + " \n", + " except socket.timeout:\n", + " print(\"close connection by timeout\")\n", + " break\n", + " \n", + " if not data:\n", + " break\n", + " \n", + " print(data.decode(\"utf8\"))" + ] + }, + { + "cell_type": "markdown", + "id": "6fdcd045", + "metadata": {}, + "source": [ + "Мы создаем сокет при помощи контекстного менеджера. Вызываем методы `bind` и `listen`. Затем в бесконечном цикле вызываем метод `accept`. И слушаем наш сокет, получаем новое соединение. После того, как мы получили это соединение, мы вызываем метод `settimeout` для нашего объекта. И передаем туда значение \"5\". По умолчанию все вызовы для этого объекта соединения, например вызов `recv`, или вызов `send`, они будут заблокированы до тех пор, пока данные на другой стороне кто-то не сможет прочитать или записать. По умолчанию таймаута нет. Мы можем передать значение `None` и это как раз будет значением по умолчанию. Если мы передадим `timeout = 0`, это будет означать немного другое. Это переведет наш сокет в неблокирующий режим. Про неблокирующий режим мы будем говорить чуть позднее. Также можно задать свой таймаут. \n", + "\n", + "Итак, если мы задали таймаут и вызвали метод `recv` у нашего сокета, и в этот сокет не поступило данных в течении пяти секунд, то будет сгенерировано исключение `socket.timeout`. Его можно перехватить и обработать. Как обрабатывать, вам нужно решать самим. Можно, например, закрывать соединение. Можно, например, если мы пишем в это соединение, повторно отправлять туда данные, или делать `retry`. Все зависит от требований к вашей программе. Но если вы работаете с таймаутами, вам нужно быть готовым к этому как на стороне сервера, так и на стороне клиента. \n", + "\n", + "Итак, в данном случае мы просто закрываем соединение. Метод `close` будет вызван автоматически, когда будет выход из нашего контекстного менеджера.\n", + "\n", + "Рассмотрим код на клиенте." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e06bb19a", + "metadata": {}, + "outputs": [], + "source": [ + "# создание сокета, таймауты и обработка ошибок\n", + "# клиент\n", + "import socket\n", + "\n", + "with socket.create_connection((\"127.0.0.1\", 10001), 5) as sock:\n", + " # set socket read timeout\n", + " sock.settimeout(2)\n", + " \n", + " try:\n", + " sock.sendall(\"ping\".encode(\"utf8\"))\n", + " \n", + " except socket.timeout:\n", + " print(\"send data timeout\")\n", + " \n", + " except socket.error as ex:\n", + " print(\"send data error:\", ex)" + ] + }, + { + "cell_type": "markdown", + "id": "80927eea", + "metadata": {}, + "source": [ + "На клиенте существует так называемый `connect timeout` и `socket read timeout`. Их еще называют именно так. `connect timeout` мы задаем в методе `create_connection` и этот таймаут будет распространяться только на установку соединения с нашим сервером. То есть если в течении 5 секунд наш сервер не смог подключить соединение, и наше соединение не было установлено, то возникнет исключение `socket.timeout` и нам на стороне клиента нужно будет его обработать. То есть сделать переподключение, подождать некоторое время и попробовать создать это соединение снова. В данном случае эти таймауты могут немножко различаться. Так как, например, на установку соединения может требоваться немного больше времени, поэтому этот таймаут может быть задан большим. После того, как соединение установлено, мы можем задать таймаут на все операции с нашим сокетом. Например, если мы не смогли записать данные, или прочитать данные с сокета, то сделать соответствующую обработку. Также может возникнуть любое другое исключение и его точно так же нужно будет обработать. Базовый класс для обработки исключений модуля `socket` - это `socket.error`. Давайте попробуем запустить наш пример и посмотрим, как работают таймауты в консоли. Для этого нам потребуется код нашего сервера. Итак, в нем мы создаем сокет и `accept`'им новое соединение. Также обрабатываем чтение из новых соединений с таймаутом. Запускаем наш пример при помощи команды python3." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29bf11f2", + "metadata": {}, + "outputs": [], + "source": [ + "! python ex1.py" + ] + }, + { + "cell_type": "markdown", + "id": "d46e4532", + "metadata": {}, + "source": [ + "И теперь перейдем к клиентской части. Запускаем консоль. Интерпретатор python3. Давайте попробуем подключиться к нашему серверу. Для этого импортируем модуль `socket`, создаем объект сокета при помощи метода `create_connection`. Указываем адресную пару. Порт 10001, который мы указали на стороне сервера. Все, наше соединение готово." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5a1a72aa", + "metadata": {}, + "outputs": [], + "source": [ + "import socket\n", + "\n", + "sock = socket.create_connection((\"127.0.0.1\", 10001))" + ] + }, + { + "cell_type": "markdown", + "id": "47e6bd08", + "metadata": {}, + "source": [ + "Давайте взглянем на сервер. Сервер уже получил `socket.timeout` и закрыл наше соединение. Давайте попробуем записать данные. Помним, что это должны быть байты." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c0369597", + "metadata": {}, + "outputs": [], + "source": [ + "sock.sendall(\"Привет\".encode(\"utf-8\"))" + ] + }, + { + "cell_type": "markdown", + "id": "4d57a6de", + "metadata": {}, + "source": [ + "На первый взгляд вроде мы записали. Но если попробуем еще раз, то получим исключение `BrokenPipeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "41223ae7", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "ename": "BrokenPipeError", + "evalue": "[Errno 32] Broken pipe", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mBrokenPipeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0msock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msendall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Привет\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mencode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"utf-8\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mBrokenPipeError\u001b[0m: [Errno 32] Broken pipe" + ] + } + ], + "source": [ + "sock.sendall(\"Привет\".encode(\"utf-8\"))" + ] + }, + { + "cell_type": "markdown", + "id": "6685acd6", + "metadata": {}, + "source": [ + "Это исключение является подклассом исключений `socket.error`. Давайте проверим это. Для этого можно воспользоваться функцией `issubclass`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1d8cdd3b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "issubclass(BrokenPipeError, socket.error)" + ] + }, + { + "cell_type": "markdown", + "id": "c4b0b5a2", + "metadata": {}, + "source": [ + "Да, это действительно так. Итак, для того, чтобы отправить данные, нам необходимо повторно переподключиться к нашему серверу и успеть в течении пяти секунд это сделать." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ad4898d0", + "metadata": {}, + "outputs": [], + "source": [ + "sock = socket.create_connection((\"127.0.0.1\", 10001))\n", + "sock.sendall(\"Привет\".encode(\"utf-8\"))" + ] + }, + { + "cell_type": "markdown", + "id": "3d9d884d", + "metadata": {}, + "source": [ + "Итак, на этой лекции мы обсудили обработку сетевых ошибок, поговорили о том, как правильно их обрабатывать. Как работать с таймаутами. Все настоящие сетевые программы должны это делать. Обязательно нужно быть готовым, что сеть может работать нестабильно, и обрабатывать правильно таймауты и сетевые ошибки.\n", + "\n", + "На следующей лекции мы поговорим о том, как на стороне сервера обработать несколько соединений одновременно." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Тест по работе с сетью и сокетами (Clear).ipynb b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Тест по работе с сетью и сокетами (Clear).ipynb new file mode 100644 index 0000000..24b5ea8 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Тест по работе с сетью и сокетами (Clear).ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f2acffb9", + "metadata": {}, + "source": [ + "# Тест по работе с сетью и сокетами #" + ] + }, + { + "cell_type": "markdown", + "id": "d917f045", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "f20b25c6", + "metadata": {}, + "source": [ + "##### 1. Для чего нужны сокеты?\n", + "\n", + "- [ ] Для организации взаимодействия процессов, работающих на разных серверах\n", + "- [ ] Для хранения общих данных нескольких потоков, работающих на одном сервере" + ] + }, + { + "cell_type": "markdown", + "id": "20a320bc", + "metadata": {}, + "source": [ + "##### 2. Если программа сервер, работающая с сокетом, написана на языке, отличном от python, возможно написать программу клиент на python для отправки данных на сервер?\n", + "\n", + "- [ ] Нет\n", + "- [ ] Да" + ] + }, + { + "cell_type": "markdown", + "id": "f2b097b8", + "metadata": {}, + "source": [ + "##### 3. Отметьте истинные утверждения:\n", + "\n", + "- [ ] контекстный менеджер автоматически выполнит вызов метода `close` для открытого сокета\n", + "- [ ] контекстный менеджер позволяет контролировать закрытие сокетов\n", + "- [ ] контекстный менеджер автоматически устанавливает новое соединение, если произойдет его разрыв\n", + "- [ ] контекстный менеджер для сокета управляет скоростью чтения из сокета" + ] + }, + { + "cell_type": "markdown", + "id": "117a3a11", + "metadata": {}, + "source": [ + "##### 4. Отметьте истинные утверждения:\n", + "\n", + "- [ ] при возникновении `socket.error` сокет автоматически закрывается\n", + "- [ ] `socket timeout` нужен для ограничения времени ожидания работы системных вызовов, связанных с сокетами\n", + "- [ ] исключение типа `socket.error` генерируется при возникновении ошибок работы с сокетами на python\n", + "- [ ] `socket timeout` нужен для установки нового соединения" + ] + }, + { + "cell_type": "markdown", + "id": "03cd9233", + "metadata": {}, + "source": [ + "##### 5. Как можно обработать большое количество одновременных соединений к серверу?\n", + "\n", + "- [ ] Создание отдельного потока для каждого нового соединения\n", + "- [ ] Процессы и потоки не нужны, python автоматически обработает все соединения\n", + "- [ ] Создание отдельного процесса для каждого соединения\n", + "- [ ] Создание пула процессов и отдельных потоков для каждого соединения" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Тест по работе с сетью и сокетами.ipynb b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Тест по работе с сетью и сокетами.ipynb new file mode 100644 index 0000000..d44beb3 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/2. Работа с сетью, сокеты/Тест по работе с сетью и сокетами.ipynb @@ -0,0 +1,94 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f2acffb9", + "metadata": {}, + "source": [ + "# Тест по работе с сетью и сокетами #" + ] + }, + { + "cell_type": "markdown", + "id": "f20b25c6", + "metadata": {}, + "source": [ + "1. Для чего нужны сокеты?\n", + "\n", + "- [x] Для организации взаимодействия процессов, работающих на разных серверах\n", + "- [ ] Для хранения общих данных нескольких потоков, работающих на одном сервере" + ] + }, + { + "cell_type": "markdown", + "id": "20a320bc", + "metadata": {}, + "source": [ + "2. Если программа сервер, работающая с сокетом, написана на языке, отличном от python, возможно написать программу клиент на python для отправки данных на сервер?\n", + "\n", + "- [ ] Нет\n", + "- [x] Да" + ] + }, + { + "cell_type": "markdown", + "id": "f2b097b8", + "metadata": {}, + "source": [ + "3. Отметьте истинные утверждения:\n", + "\n", + "- [x] контекстный менеджер автоматически выполнит вызов метода `close` для открытого сокета\n", + "- [x] контекстный менеджер позволяет контролировать закрытие сокетов\n", + "- [ ] контекстный менеджер автоматически устанавливает новое соединение, если произойдет его разрыв\n", + "- [ ] контекстный менеджер для сокета управляет скоростью чтения из сокета" + ] + }, + { + "cell_type": "markdown", + "id": "117a3a11", + "metadata": {}, + "source": [ + "4. Отметьте истинные утверждения:\n", + "\n", + "- [ ] при возникновении `socket.error` сокет автоматически закрывается\n", + "- [x] `socket timeout` нужен для ограничения времени ожидания работы системных вызовов, связанных с сокетами\n", + "- [x] исключение типа `socket.error` генерируется при возникновении ошибок работы с сокетами на python\n", + "- [ ] `socket timeout` нужен для установки нового соединения" + ] + }, + { + "cell_type": "markdown", + "id": "03cd9233", + "metadata": {}, + "source": [ + "5. Как можно обработать большое количество одновременных соединений к серверу?\n", + "\n", + "- [x] Создание отдельного потока для каждого нового соединения\n", + "- [ ] Процессы и потоки не нужны, python автоматически обработает все соединения\n", + "- [x] Создание отдельного процесса для каждого соединения\n", + "- [x] Создание пула процессов и отдельных потоков для каждого соединения" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/client.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/client.py new file mode 100644 index 0000000..cf30c06 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/client.py @@ -0,0 +1,93 @@ +"""Реализация клиента для сервера метрик""" + +import socket +from time import time +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Dict, List, Optional, Tuple + + +class ClientError(Exception): + """Общий класс исключений клиента""" + + ... + + +class ClientSocketError(ClientError): + """Исключение, выбрасываемое клиентом при сетевой ошибке""" + + ... + + +class ClientProtocolError(ClientError): + """Исключение, выбрасываемое клиентом при ошибке протокола""" + + ... + + +class Client: + """Класс клиент для сервера метрик""" + + def __init__( + self, host: "str", port: "int", timeout: "Optional[int]" = None + ): + """Конструктор класса""" + + self.connection: "Tuple[str,int]" = (host, port) + self.timeout: "Optional[int]" = timeout + + def send(self, cmd: "str") -> "str": + """Отправка команд серверу""" + data: "bytes" = b"" + + try: + with socket.create_connection( + self.connection, self.timeout + ) as sock: + sock.sendall(cmd.encode("utf8")) + + while not data.endswith(b"\n\n"): + data += sock.recv(1024) + + except socket.error as err: + raise ClientSocketError("error create connection", err) from err + + status, payload = data.decode("utf-8").split("\n", 1) + payload: "str" = payload.strip() + + if status == "error": + raise ClientProtocolError(payload) + + return payload + + def put( + self, metric: "str", value: "float", timestamp: "Optional[int]" = None + ) -> "None": + """Метод отправки данных""" + + self.send( + f"put {metric} {value} {timestamp if timestamp else int(time())}\n" + ) + + def get(self, metric: "str") -> "Dict[str,List[Tuple[int,float]]]": + """Метод получения данных""" + + result: "Dict[str,List[Tuple[int,float]]]" = {} + + for line in self.send(f"get {metric}\n").splitlines(): + try: + _metric, value, timestamp = line.split() + + if not _metric in result: + result[_metric] = [] + + result[_metric].append((int(timestamp), float(value))) + + except ValueError as error: + raise ClientProtocolError(line) from error + + for item in result.items(): + item[1].sort(key=lambda stamp: stamp[0]) + + return result diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex1.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex1.py new file mode 100644 index 0000000..5ee0f74 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex1.py @@ -0,0 +1,40 @@ +# Неблокирующий ввод/вывод, обучающий пример +import socket +import select + +sock = socket.socket() +sock.bind(("", 10001)) +sock.listen() + +# как обработать запросы для conn1 и conn2 +# одновременно без потоков? +conn1, addr = sock.accept() +conn2, addr = sock.accept() + +conn1.setblocking(0) +conn2.setblocking(0) + +epoll = select.epoll() +epoll.register(conn1.fileno(), select.EPOLLIN | select.EPOLLOUT) +epoll.register(conn2.fileno(), select.EPOLLIN | select.EPOLLOUT) + +conn_map = { + conn1.fileno(): conn1, + conn2.fileno(): conn2, +} + +# Неблокирующий ввод/вывод, обучающий пример +# Цикл обработки событий в epoll + +while True: + events = epoll.poll(1) + + for fileno, event in events: + if event & select.EPOLLIN: + # обработка чтения из сокета + data=conn_map[fileno].recv(1024) + print(data.decode("utf8")) + + elif event & select.EPOLLOUT: + # обработка записи в сокет + conn_map[fileno].send("pong".encode("utf8")) \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex10.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex10.py new file mode 100644 index 0000000..3eacc1b --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex10.py @@ -0,0 +1,15 @@ +# loop.run_in_executor, запуск в отдельном потоке +import asyncio +from urllib.request import urlopen + +# a synchronous function +def sync_get_url(url): + return urlopen(url).read() + +async def load_url(url, loop=None): + future = loop.run_in_executor(None, sync_get_url, url) + response = await future + print(len(response)) + +loop = asyncio.get_event_loop() +loop.run_until_complete(load_url("http://sinfo/", loop=loop)) \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex2.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex2.py new file mode 100644 index 0000000..308f059 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex2.py @@ -0,0 +1,21 @@ +# Итераторы +class MyRangeIterator: + def __init__(self, top): + self.top = top + self.current = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.current >= self.top: + raise StopIteration + + current = self.current + self.current += 1 + + return current + +counter = MyRangeIterator(3) +for it in counter: + print(it) \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex3.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex3.py new file mode 100644 index 0000000..2cc6089 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex3.py @@ -0,0 +1,10 @@ +# Генераторы +def my_range_generator(top): + current = 0 + while current < top: + yield current + current += 1 + +counter = my_range_generator(3) +for it in counter: + print(it) \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex4.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex4.py new file mode 100644 index 0000000..df86219 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex4.py @@ -0,0 +1,13 @@ +# Сопрограммы (корутины) +def grep(pattern): + print("start grep") + while True: + line = yield + + if pattern in line: + print(line) + +g = grep("python") +next(g) +g.send("golang is better?") +g.send("python is simple!") \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex5.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex5.py new file mode 100644 index 0000000..912ba27 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex5.py @@ -0,0 +1,12 @@ +# asyncio, Hello World +import asyncio + +@asyncio.coroutine +def hello_world(): + while True: + print("Hello World!") + yield from asyncio.sleep(1.0) + +loop = asyncio.get_event_loop() +loop.run_until_complete(hello_world()) +loop.close() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex6.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex6.py new file mode 100644 index 0000000..bd53263 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex6.py @@ -0,0 +1,24 @@ +# asyncio, tcp сервер +import asyncio + +async def handle_echo(reader, writer): + data = await reader.read(1024) + message = data.decode() + addr = writer.get_extra_info("peername") + print(f"received {message} from {addr}") + writer.close() + +loop = asyncio.get_event_loop() +coro = asyncio.start_server(handle_echo, "127.0.0.1", 10001, loop=loop) +server = loop.run_until_complete(coro) + +try: + loop.run_forever() + +except KeyboardInterrupt: + pass + +server.close() + +loop.run_until_complete(server.wait_closed()) +loop.close() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex7.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex7.py new file mode 100644 index 0000000..91b06f7 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex7.py @@ -0,0 +1,13 @@ +# asyncio, tcp клиент +import asyncio + +async def tcp_echo_client(message, loop): + reader, writer = await asyncio.open_connection("127.0.0.1", 10001, loop=loop) + print("send: %r" % message) + writer.write(message.encode()) + writer.close() + +loop = asyncio.get_event_loop() +message = "hello World!" +loop.run_until_complete(tcp_echo_client(message, loop)) +loop.close() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex8.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex8.py new file mode 100644 index 0000000..6866f43 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex8.py @@ -0,0 +1,15 @@ +### asyncio.Future, аналог concurrent.futures.Future +import asyncio + +async def slow_operation(future): + await asyncio.sleep(1) + future.set_result("Future is done!") + +loop = asyncio.get_event_loop() +future = asyncio.Future() +asyncio.ensure_future(slow_operation(future)) + +loop.run_until_complete(future) +print(future.result()) + +loop.close() \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex9.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex9.py new file mode 100644 index 0000000..93ad312 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex9.py @@ -0,0 +1,21 @@ +### asyncio.Task, запуск нескольких корутин +import asyncio + +async def sleep_task(num): + for i in range(5): + print(f"process task: {num} iter: {i}") + await asyncio.sleep(1) + + return num + +# ensure_future or create_task + +loop = asyncio.get_event_loop() +task_list = [loop.create_task(sleep_task(i)) for i in range(2)] +loop.run_until_complete(asyncio.wait(task_list)) + +loop.run_until_complete(loop.create_task(sleep_task(3))) +loop.run_until_complete(asyncio.gather( + sleep_task(10), + sleep_task(20), +)) \ No newline at end of file diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/test_client.py b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/test_client.py new file mode 100644 index 0000000..477852d --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/test_client.py @@ -0,0 +1,157 @@ +""" + Это unittest для тестирования вашего класса Client из задания. + + Для запуска теста на локальном компьютере разместите код unittest-та + и код решения в одном каталоге. Запустите тест при помощи команды: + + python -m unittest test_week5.py + + Обратите внимание на то, что ваш модуль должен называться client.py. + Это не обязательное требование, если вы назвали мобуль по-другому, то + просто измените его импорт в строке 25 на: + from you_module_name import Client, ClientError + + Модуль должен содержать классы Client и ClientError. + + Этот unittest поможет вам выполнить задание. + Успехов! +""" + +import unittest +from unittest.mock import patch +from collections import deque + +# импорт модуля с решением +from client import Client, ClientError + + +class ServerSocketException(Exception): + pass + + +class ServerSocket: + """Mock socket module""" + + def __init__(self): + self.response_buf = deque() + self.rsp_map = { + b'put test 0.5 1\n': b'ok\n\n', + b'put test 2.0 2\n': b'ok\n\n', + b'put test 0.4 2\n': b'ok\n\n', + b'put load 301 3\n': b'ok\n\n', + b'get key_not_exists\n': b'ok\n\n', + b'get test\n': b'ok\n' + b'test 0.5 1\n' + b'test 0.4 2\n\n', + b'get get_client_error\n': b'error\nwrong command\n\n', + b'get *\n': b'ok\n' + b'test 0.5 1\n' + b'test 0.4 2\n' + b'load 301 3\n\n', + } + + def sendall(self, data): + return self.send(data) + + def send(self, data): + if data in self.rsp_map: + self.response_buf.append(self.rsp_map[data]) + else: + raise ServerSocketException(f"запрос не соответствует протоколу: {data}") + + def recv(self, bytes_count): + try: + rsp = self.response_buf.popleft() + except IndexError: + raise ServerSocketException("нет данных в сокете для чтения ответа") + + return rsp + + @classmethod + def create_connection(cls, *args, **kwargs): + return cls() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + def __getattr__(self, feature): + """ignore socket.connect, soket.bind, etc...""" + pass + + +class TestClient(unittest.TestCase): + @classmethod + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def setUpClass(cls): + cls.client = Client("127.0.0.1", 10000, timeout=2) + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_put(self): + metrics_for_put = [ + ("test", 0.5, 1), + ("test", 2.0, 2), + ("test", 0.4, 2), + ("load", 301, 3), + ] + for metric, value, timestamp in metrics_for_put: + try: + self.client.put(metric, value, timestamp) + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Ошибка вызова client.put(" + f"'{metric}', {value}, timestamp={timestamp})\n{message}") + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_get_key(self): + try: + rsp = self.client.get("test") + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Ошибка вызова client.get('test')\n{message}") + + metrics_fixture = { + "test": [(1, .5), (2, .4)], + } + self.assertEqual(rsp, metrics_fixture) + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_get_all(self): + try: + rsp = self.client.get("*") + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Ошибка вызова client.get('*')\n{message}") + + metrics_fixture = { + "test": [(1, .5), (2, .4)], + "load": [(3, 301.0)] + } + self.assertEqual(rsp, metrics_fixture) + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_get_not_exists(self): + try: + rsp = self.client.get("key_not_exists") + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Ошибка вызова client.get('key_not_exists')\n{message}") + + self.assertEqual({}, rsp, "check rsp eq {}") + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_get_client_error(self): + try: + self.assertRaises(ClientError, + self.client.get, "get_client_error") + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Некорректно обработано сообщение сервера об ошибке: {message}") diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Генераторы и сопрограммы.ipynb b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Генераторы и сопрограммы.ipynb new file mode 100644 index 0000000..7da696c --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Генераторы и сопрограммы.ipynb @@ -0,0 +1,608 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "888a9140", + "metadata": {}, + "source": [ + "# Генераторы и сопрограммы #" + ] + }, + { + "cell_type": "markdown", + "id": "fd782031", + "metadata": {}, + "source": [ + "На предыдущей лекции мы рассмотрели то, как работают генераторы в Python. На этой лекции мы познакомимся с сопрограммами и выясним, в чем отличие и сходство между генераторами и сопрограммами в Python. Также мы обсудим, как работает конструкция `yield from` и рассмотрим примеры работы различных сопрограмм.\n", + "\n", + "Итак, давайте перейдем к примеру и рассмотрим, как выглядит сопрограмма." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5ac63f59", + "metadata": {}, + "outputs": [], + "source": [ + "# Сопрограммы (корутины)\n", + "def grep(pattern):\n", + " print(\"start grep\")\n", + " while True:\n", + " line = yield\n", + " \n", + " if pattern in line:\n", + " print(line)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ba7628fb", + "metadata": {}, + "outputs": [], + "source": [ + "g = grep(\"python\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cb95ffaa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "start grep\n" + ] + } + ], + "source": [ + "next(g)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2ba2e231", + "metadata": {}, + "outputs": [], + "source": [ + "g.send(\"golang is better?\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ced3ecae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "python is simple!\n" + ] + } + ], + "source": [ + "g.send(\"python is simple!\")" + ] + }, + { + "cell_type": "markdown", + "id": "0bcba4fa", + "metadata": {}, + "source": [ + "Предположим, наша цель — фильтровать входной поток при помощи функции `grep`. В функцию `grep` мы передаем некий паттерн, например, это пусть будет строчка `python`. Далее мы должны в эту функцию передать некие строчки и вывести на экран только те, в которых присутствует строка `python`. При помощи сопрограмм наша функция `grep` выглядит следующим образом.\n", + "\n", + "Мы в бесконечном цикле вызываем такую конструкцию - `line` присвоить `yield`. В этом месте очень важно уделить внимание отличию от того, как `yield` используется в генераторах. В генераторах мы выражение присваивали справа от конструкции `yield`, а здесь мы, наоборот, переменной присваиваем результат работы конструкции `yield`.\n", + "\n", + "Что произойдет в данном случае? В данном случае функция как бы заморозит свое значение и будет ожидать ввода данных при помощи метода `send`. Итак, если мы вызовем функцию `grep`, будет создана корутина. Для того чтобы запустить нашу корутину, необходимо вызвать метод `next`. После того, как метод `next` будет вызван, запустится код нашей функции, выведется строчка `start grep`, запустится бесконечный цикл, код дойдет до инструкции `yield`, и здесь вернется управление в основной поток.\n", + "\n", + "После этого в основном потоке мы отправляем данные нашей корутине, и код возобновляет свою работу дальше. Давайте посмотрим, как код будет выполняться в отладчике в консоли. Нам потребуется код нашего примера этой утилиты — это наша корутина `grep`. Запускаем отладчик при помощи команды python3, указываем флаг `−m pdb` и запускаем наш отладчик." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2fa9a73", + "metadata": {}, + "outputs": [], + "source": [ + "! python -m pdb ex4.py" + ] + }, + { + "cell_type": "markdown", + "id": "5cb7ca6c", + "metadata": {}, + "source": [ + "Давайте разбираться с тем, как будет выполняться наша корутина. Итак, мы объявили нашу функцию `grep`. Далее, мы создали объект типа генератор. Давайте еще раз убедимся, что это действительно генератор. Наш объект `g`, после того как ему присвоили выражение `grep(\"python\")`, создался, а вызова пока еще не произошло никакого для этой функции. Далее мы вызываем метод `next(g)`, и это будет равносильно тому, что мы бы вызвали метод `g.send` и туда передали значение `None` и эта конструкция запустит нашу корутину.\n", + "\n", + "Итак, мы провалились в нашу функцию, вывели в консоль строчку \"start grep\", зашли в наш цикл, дошли до инструкции `yield`, и здесь произошел возврат. Хочу обратить ваше внимание, как произошел этот возврат. Он не равносилен инструкции `return` для функции. Дело в том, что в объекте `g`, который является корутиной, запомнился указатель на фрейм стека, запомнились все локальные переменные. Функция помнит свое состояние. Она как бы заморозила свое выполнение в том месте, где была указана инструкция `yield`. Теперь наша корутина запущена, она помнит свое состояние, и она ожидает, пока кто-нибудь вызовет метод `send` и отправит в нее данные. Давайте продолжим выполнение. Итак, мы отправили данные при помощи вызова `send`. Мы опять вернулись в нашу функцию, но вернулись именно в то место, где завершили или заморозили свою работу. Это инструкция `yield`. Инструкций `yield` может быть несколько. Давайте посмотрим, чему равна переменная `line`. В данном случае наше условие для фильтрации не будет выполнено, и на экран ничего не напечатается. Давайте продолжим выполнение. Мы снова в цикле продолжаем работу этой нашей корутины, снова выполняем строчку `line` присвоить `yield`, возвращаемся из нашей корутины. В основном потоке мы опять отправляем данные в нашу корутину. В данном случае это будет строчка \"python is simple\". Опять проваливаемся в нашу функцию. Давайте посмотрим, чему равна переменная `line`. Да, действительно, это то, что мы в нее отправили при помощи метода `send`. В данном случае наша корутина напечатает на экран эту строчку. Все, я сейчас запустил все заново. На самом деле у функции `g` наша программа прекратила свою работу.\n", + "\n", + "Итак, давайте еще раз остановимся на ключевых моментах и отличии того, как работает корутина по сравнению с генератором. Здесь мы видим немного другое использование инструкции `yield`. Мы в переменную присваиваем ее значение. Внутри корутины она не производит значение, она потребляет значение, и точно так же приостанавливает или замораживает свое состояние, ждет, пока это значение в нее поступит. После поступления значения она возобновляет свою работу. Давайте продолжим изучать особенности корутин и посмотрим следующий пример." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8a676c5e", + "metadata": {}, + "outputs": [], + "source": [ + "# Сопрограммы, вызов метода close()\n", + "def grep(pattern):\n", + " print(\"start grep\")\n", + " try:\n", + " while True:\n", + " line = yield\n", + " if pattern in line:\n", + " print(line)\n", + " \n", + " except GeneratorExit:\n", + " print(\"stop grep\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0b51a749", + "metadata": {}, + "outputs": [], + "source": [ + "g = grep(\"python\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c5133a0a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "start grep\n" + ] + } + ], + "source": [ + "next(g)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0a2d7d0a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "python is the best!\n" + ] + } + ], + "source": [ + "g.send(\"python is the best!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7b508e1f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "stop grep\n" + ] + } + ], + "source": [ + "g.close()" + ] + }, + { + "cell_type": "markdown", + "id": "fde51a2c", + "metadata": {}, + "source": [ + "Иногда необходимо остановить запущенную корутину. Делается это при помощи вызова метода `close` для объекта корутины. Метод `close` будет вызван автоматически сборщиком мусора, но если нам нужно самим остановить корутину, то можно руками вызвать метод `close`. Метод `close` сгенерирует исключение генератора `exit` в том месте, где функция заморозила свое значение, свою работу.\n", + "\n", + "Это исключение нельзя игнорировать, его нужно обрабатывать и завершать свою работу. Обрабатывать его можно при помощи стандартной работы с исключениями, при помощи блока `try except`.\n", + "\n", + "Иногда необходимо передать исключения в саму корутину. Это делается при помощи вызова метода `throw`. Пример точно такой же." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7f4a00f5", + "metadata": {}, + "outputs": [], + "source": [ + "# Сопрограммы, генерация исключений\n", + "def grep(pattern):\n", + " print(\"start grep\")\n", + " try:\n", + " while True:\n", + " line = yield\n", + " if pattern in line:\n", + " print(line)\n", + " \n", + " except GeneratorExit:\n", + " print(\"stop grep\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "84247ff8", + "metadata": {}, + "outputs": [], + "source": [ + "g = grep(\"python\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "05476865", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "start grep\n" + ] + } + ], + "source": [ + "next(g)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "17031b8a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "python is the best!\n" + ] + } + ], + "source": [ + "g.send(\"python is the best!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "bfabf7a3", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "ename": "RuntimeError", + "evalue": "something wrong", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mthrow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mRuntimeError\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"something wrong\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mgrep\u001b[0;34m(pattern)\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mline\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32myield\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mpattern\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mline\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mline\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mRuntimeError\u001b[0m: something wrong" + ] + } + ], + "source": [ + "g.throw(RuntimeError, \"something wrong\")" + ] + }, + { + "cell_type": "markdown", + "id": "59273372", + "metadata": {}, + "source": [ + "Наша корутина приостановила свою работу на вот этой инструкции, и если мы отправим в нее исключение, то оно будет сгенерировано именно в этом месте. Сгенерированное исключение можно обработать при помощи блока `try except`, в общем, стандартным способом для языка Python.\n", + "\n", + "Давайте посмотрим еще более сложный пример." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "20a857c8", + "metadata": {}, + "outputs": [], + "source": [ + "# Вызовы сопрограмм, PEP 380\n", + "def grep(pattern):\n", + " print(\"start grep\")\n", + " while True:\n", + " line = yield\n", + " if pattern in line:\n", + " print(line)\n", + " \n", + "def grep_python_coroutine():\n", + " g = grep(\"python\")\n", + " next(g)\n", + " g.send(\"python is the best!\")\n", + " g.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "3bb19540", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "start grep\n", + "python is the best!\n" + ] + } + ], + "source": [ + "g = grep_python_coroutine() # is g coroutine?" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "8938caa3", + "metadata": {}, + "outputs": [], + "source": [ + "g" + ] + }, + { + "cell_type": "markdown", + "id": "75a1805d", + "metadata": {}, + "source": [ + "У нас есть функция `grep`, У нас есть корутина `grep`, у нее внутри есть инструкция `yield`, и мы хотим вызвать нашу корутину `grep` в другой корутине. Как это сделать? \n", + "\n", + "Если мы напишем код, который приведен на слайде, и потом сделаем данный вызов, то будет ли переменная `g` являться корутиной? На самом деле, не будет. Так как она не содержит инструкции `yield`, она выполнится сразу, она будет являться обычной функцией. Для того чтобы было удобно вызывать из одних корутин другие, в Python разработали стандарт `PEP 0380` и в Python 3 его реализовали. И теперь в Python 3 появилась инструкция `yield from`. При помощи нее можно выполнить делегирование вызова одной корутины в другой. Наш пример можно переписать следующим образом." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "f9c1c496", + "metadata": {}, + "outputs": [], + "source": [ + "# Сопрограммы, yield from PEP 0380\n", + "def grep(pattern):\n", + " print(\"start grep\")\n", + " while True:\n", + " line = yield\n", + " if pattern in line:\n", + " print(line)\n", + " \n", + "def grep_python_coroutine():\n", + " g = grep(\"python\")\n", + " yield from g" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "6e7ce4c8", + "metadata": {}, + "outputs": [], + "source": [ + "g = grep_python_coroutine() # is g coroutine?" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "6c4e848e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "9c818942", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "start grep\n" + ] + } + ], + "source": [ + "g.send(None)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "d5052d2c", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "python wow!\n" + ] + } + ], + "source": [ + "g.send(\"python wow!\")" + ] + }, + { + "cell_type": "markdown", + "id": "ff9ded26", + "metadata": {}, + "source": [ + "То есть, мы используем инструкцию `yield from` и указываем объект в виде другой корутины. Теперь, если мы попробуем вызвать нашу функцию `grep_python_coroutine`, она будет являться корутиной или генератором, и для того, чтобы продолжить и выполнить код, который находится у нее внутри, необходимо вызвать метод `send`, передать туда значение `none` и далее вызвать метод `send` и передать нужную строчку. Таким образом, в Python стало очень легко и удобно вызывать из одной корутины другую.\n", + "\n", + "Для обычных генераторов инструкцию `yield` можно использовать как замену цикла `for`, внутри которого вызывается инструкция `yield`. Рассмотрим пример." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "ffe0106e", + "metadata": {}, + "outputs": [], + "source": [ + "# PEP 380, генераторы\n", + "def chain(x_iterable, y_iterable):\n", + " yield from x_iterable\n", + " yield from y_iterable" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "47f4f8b9", + "metadata": {}, + "outputs": [], + "source": [ + "a = [1, 2, 3]" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "00dc65e5", + "metadata": {}, + "outputs": [], + "source": [ + "b = (4, 5)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "f92a3508", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n", + "3\n", + "4\n", + "5\n" + ] + } + ], + "source": [ + "for x in chain(a, b):\n", + " print(x)" + ] + }, + { + "cell_type": "markdown", + "id": "21998335", + "metadata": {}, + "source": [ + "Например, у нас есть два объекта, по которым возможно осуществлять итерацию. Это список `a = [1, 2, 3]` и `tuple`, который мы запоминаем в переменную `b = (4, 5)`. Далее, мы в цикле вызываем функцию `chain`, передаем туда эти списки и выводим то, что нам генерирует наша функция `chain`. В функции `chain` мы используем конструкцию `yield from` и передаем туда объект, по которому возможна итерация. По сути, это то же самое, если мы функцию перепишем в таком виде" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a82b907", + "metadata": {}, + "outputs": [], + "source": [ + "def the_same_chain(x_iterable, y_iterable):\n", + " for x in x_iterable:\n", + " yield x\n", + " \n", + " for y in y_iterable:\n", + " yield y" + ] + }, + { + "cell_type": "markdown", + "id": "f8ff665d", + "metadata": {}, + "source": [ + "то `yield from` будет работать точно так же, как цикл `for`, внутри которого вызвана инструкция `yield`." + ] + }, + { + "cell_type": "markdown", + "id": "c754a0cd", + "metadata": {}, + "source": [ + "Итак, на этой лекции мы рассмотрели то, как устроены корутины в Python, а также рассмотрели их сходство с генераторами и различия.\n", + "\n", + "Еще раз хочу обратить ваше внимание на то, что генераторы производят значение, а корутины потребляют значение. Корутина может замораживать свое состояние, и восстанавливается это состояние, после того как в нее отправили данные при помощи вызова метода `send`. При помощи вызова `close` можно завершать выполнение корутины, передавать исключения в нее. Также в Python 3 можно использовать конструкцию `yield from` для вызова одной корутины в другой. Все эти знания потребуются нам для того, чтобы понять, как устроен фреймворк `asyncio` и в следующих лекциях мы попробуем разобраться с устройством этого фреймворка." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Документация.ipynb b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Документация.ipynb new file mode 100644 index 0000000..25c3ed5 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Документация.ipynb @@ -0,0 +1,44 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ca2d5d99", + "metadata": {}, + "source": [ + "# Документация #" + ] + }, + { + "cell_type": "markdown", + "id": "ffe07ebd", + "metadata": {}, + "source": [ + "- [Модуль select](https://docs.python.org/3.6/library/select.html \"18.3. select — Waiting for I/O completion\")\n", + "- [Делегирование вызова генератора pep-0380](https://www.python.org/dev/peps/pep-0380/ \"PEP 380 -- Syntax for Delegating to a Subgenerator\")\n", + "- [Asyncio](https://docs.python.org/3.6/library/asyncio.html \"18.5. asyncio — Asynchronous I/O, event loop, coroutines and tasks\")\n", + "- [Примеры для asyncio](https://habrahabr.ru/post/217143/ \"Примеры использования asyncio: HTTPServer?!\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Исполнение кода в одном потоке, модуль select.ipynb b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Исполнение кода в одном потоке, модуль select.ipynb new file mode 100644 index 0000000..02a2ba7 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Исполнение кода в одном потоке, модуль select.ipynb @@ -0,0 +1,205 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d3abcf81", + "metadata": {}, + "source": [ + "# Исполнение кода в одном потоке, модуль select #" + ] + }, + { + "cell_type": "markdown", + "id": "5f549add", + "metadata": {}, + "source": [ + "Итак, на этой лекции мы поговорим об исполнении кода в один поток. В предыдущих лекциях мы рассмотрели использование процессов и потоков для организации взаимодействия большого количества клиентов по сети сервера. Исполнение кода в один поток — это немного другой подход, и сегодня мы остановим внимание на использовании модуля `select`, поговорим про неблокирующий ввод-вывод в Python, а также обсудим некоторые популярные фреймворки в Python.\n", + "\n", + "В операционной системе существует модуль `select`, который позволяет организовать работу с неблокирующим вводом-выводом. Что это значит, мы сейчас будем разбирать на примерах.\n", + "\n", + "Отдельно хочется сказать, что существует несколько подходов или механизмов опроса всех файловых дескрипторов для организации неблокирующего ввода-вывода. Эти методы перечислены на слайде: это `select.select`, `select.poll`, `select.epoll` и `select.kqueue`. Все эти методы связаны с особенностями операционных систем. Например, в Linux, как правило, используют `epoll`, в других операционных системах могут использоваться другие механизмы. Мы будем рассматривать примеры на основе `epoll`.\n", + "\n", + "Итак, давайте вспомним наш предыдущий пример и попробуем реализовать его немного по-другому. Вспомним, что у нас была проблема создания сокета и обработки нескольких входящих соединений одновременно. Давайте попробуем сделать это при помощи модуля `select`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3bd1dd7", + "metadata": {}, + "outputs": [], + "source": [ + "# Неблокирующий ввод/вывод, обучающий пример\n", + "import socket\n", + "import select\n", + "\n", + "sock = socket.socket()\n", + "sock.bind((\"\", 10001))\n", + "sock.listen()\n", + "\n", + "# как обработать запросы для conn1 и conn2\n", + "# одновременно без потоков?\n", + "conn1, addr = sock.accept()\n", + "conn2, addr = sock.accept()\n", + "\n", + "conn1.setblocking(0)\n", + "conn2.setblocking(0)\n", + "\n", + "epoll = select.epoll()\n", + "epoll.register(conn1.fileno(), select.EPOLLIN | select.EPOLLOUT)\n", + "epoll.register(conn2.fileno(), select.EPOLLIN | select.EPOLLOUT)\n", + "\n", + "conn_map = {\n", + " conn1.fileno(): conn1,\n", + " conn2.fileno(): conn2,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "f492bfd6", + "metadata": {}, + "source": [ + "Итак, предположим, мы создали объект класс `socket`, вызвали метод `bind`, вызвали метод `listen` и смогли вызвать метод `accept` для двух соединений. Теперь у нас есть два объекта соединения — `connection1` и `connection2`, и нам необходимо одновременно читать или записывать данные из этих соединений без использования потоков или процессов. Как можно это организовать в Linux? Для этого необходимо перевести наше соединение, во-первых, в неблокирующий режим. Делается это при помощи вызова `setblocking(0)`. Это равносильно тому, что мы сделаем вызов `settimeout(0)`. Теперь наши сокеты в неблокирующем режиме, и если мы попробуем что-то прочитать из этого сокета, а там данных нет, то наш вызов `recv` не заблокируется, а вернет нам некую системную ошибку, которая будет говорить о том, что пока данных нет, они еще не поступили. Но как узнать, какие сокеты готовы читать, а какие сокеты готовы записывать данные? Для этого как раз нам понадобится объект `epoll`. Итак, создаем объект `epoll` при помощи модуля `select`. Далее мы регистрируем в этом объекте `epoll` наши файловые дескрипторы от созданных коннектов клиентских, а также мы регистрируем некую маску. Говорим, на какие события мы подписываемся от этих файловых дескрипторов. В данном случае это чтение из сокетов и запись в сокет. Далее для организации цикла опроса событий нам необходимо запомнить наши объекты и смапить их по файловым дескрипторам. То есть формируем словарик, в него записываем файловые дескрипторы и объекты наших соединений. Давайте рассмотрим цикл обработки событий для нашего `epoll`, еще иногда его называют `event_loop`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18ada3b9", + "metadata": {}, + "outputs": [], + "source": [ + "# Неблокирующий ввод/вывод, обучающий пример\n", + "# Цикл обработки событий в epoll\n", + "\n", + "while True:\n", + " events = epoll.poll(1)\n", + " \n", + " for fileno, event in events:\n", + " if event & select.EPOLLIN:\n", + " # обработка чтения из сокета\n", + " data=conn_map[fileno].recv(1024)\n", + " print(data.decode(\"utf8\"))\n", + " \n", + " elif event & select.EPOLLOUT:\n", + " # обработка записи в сокет\n", + " conn_map[fileno].send(\"pong\".encode(\"utf8\"))" + ] + }, + { + "cell_type": "markdown", + "id": "30878ebd", + "metadata": {}, + "source": [ + "Итак, мы в бесконечном цикле постоянно опрашиваем наш объект `epoll`. Это системный вызов, который нам возвращает список событий, и эти события содержат файловый дескриптор и непосредственно то событие, которое произошло с этим файловым дескриптором. Другими словами, мы постоянно просим наш объект `epoll`, говорим ему: верни нам список сокетов, которые готовы читать либо которые готовы записывать данные. После того как нам `epoll` вернул этот список, мы должны проверить, что за событие пришло, из нашего словаря получить нужный нам объект для работы с ним и сделать нужные нам действия. В данном случае, например, мы читаем данные из этого сокета и просто выводим их в консоль. если пришло событие, которое говорит нам, что сокет готов принять данные от нас, мы должны записать данные в этот сокет. Давайте попробуем запустить этот пример и посмотрим, как он действительно работает в консоли.\n", + "\n", + "Итак, нам потребуется код нашего сервера, запускаем его при помощи команды python3, и для того чтобы работал, необходимо создать два клиентских подключения." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82247ea9", + "metadata": {}, + "outputs": [], + "source": [ + "! python ex1.py" + ] + }, + { + "cell_type": "markdown", + "id": "eafad08e", + "metadata": {}, + "source": [ + "Давайте попробуем это сделать прямо в консоли, создаем объект класса `socket`, передаем адресную пару, порт. Итак, один клиент у нас уже готов для взаимодействия с нашим сервером. Давайте создадим еще один клиент. Точно так же мы создали второй клиент Теперь мы можем записать данные непосредственно в `socket`. Итак, записываем данные во втором клиенте, например, строчку `client2`, отправляем, смотрим — наш сервер обработал второе соединение. Затем можем сделать то же самое и в первом клиенте." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "def7b30c", + "metadata": {}, + "outputs": [], + "source": [ + "import socket\n", + "\n", + "sock = socket.create_connection((\"127.0.0.1\", 10001))\n", + "sock.sendall(b\"client1\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d6563052", + "metadata": {}, + "outputs": [], + "source": [ + "sock2 = socket.create_connection((\"127.0.0.1\", 10001))\n", + "sock2.sendall(b\"client2\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c2f19409", + "metadata": {}, + "outputs": [], + "source": [ + "sock2.close()\n", + "sock.close()" + ] + }, + { + "cell_type": "markdown", + "id": "222607a8", + "metadata": {}, + "source": [ + "Как мы видим, сервер успешно обработал оба соединения, причем он не использовал ни процессы, ни потоки.\n", + "\n", + "Основная идея заключается в том, что у нас есть `event_loop`, есть `epoll` который позволяет нам получить все нужные события, и при помощи `epoll` можно организовать параллельную обработку всех запросов.\n", + "\n", + "Итак, давайте обсудим плюсы и минусы того подхода, который мы сейчас обсудили и запустили в консоли. Итак, код выглядит уже не таким простым, как в случае с потоками или процессами. Появился цикл событий, появились вот эти условия для обработки типов событий, которые там произошли. Наш код носит обучающий характер, и он достаточно простой, то есть если он будет приближен к настоящему коду, который работает с сетевыми соединениями, он очень сильно усложнится. Например, в нашем коде нет обработки закрытия сокетов, а также отсутствует обработка новых входящих соединений. Заметьте, мы ведь точно так же должны перевести и сокет, на котором ждем входящее соединение, в неблокирующий режим и точно так же поместить его в наш цикл обработки событий для того, чтобы ожидать новых клиентов.\n", + "\n", + "Давайте предположим, что в обработке наших запросов будет не только вывод данных, которые мы получили в сети, в `log` или в терминал, а нам нужно будет, например, записать их в какую-то базу данных. Или, например, выполнить http-запрос. В таком случае нам нужно будет организовать ввод-вывод и всю работу с ним точно так же в неблокирующем режиме. То есть нам надо все сокеты из базы данных поместить в наш `event_loop`, или в наш цикл событий, и точно так же работать с ними в неблокирующем режиме. Тогда код станет уже не таким простым. Точно так же любые сторонние библиотеки, если мы будем использовать, например, `urllib` или библиотеку `requests`, мы тоже будем вынуждены как-то заставить их работать с нашим циклом событий и использовать `epoll` для ожидания готовности чтения и записи в сокеты, которые они используют.\n", + "\n", + "Но тем не менее, очевидным плюсом данного подхода является то, что мы, во-первых, не создаем ни процессы, ни потоки, мы не тратим память дополнительную, а также, что немаловажно, мы не тратим никаких усилий для организации взаимодействия между нашими потоками: у нас нет блокировок, код будет исполняться максимально быстро.\n", + "\n", + "Также у нас нет проблем с `GIL`. Как же можно поступить, чтобы упростить наш код? Можно спрятать все вызовы `select.epoll` в какие-то библиотеки. Так поступили и написали существующие в Python фреймворки. Давайте обсудим некоторые из них.\n", + "\n", + "Наиболее популярным был долгое время фреймворк `Twisted`, его работа очень похожа на наш пример, но код на `Twisted` выглядит достаточно сложным из-за наличия вот этих `callback`'ов и логики обработки запросов.\n", + "\n", + "Чуть позже появился фреймворк под названием `Gevent`. `Gevent` напоминал клон Python'а, который назывался `Stackless Python`, и в основу фреймворка `Gevent` положили работу гринлетов — это так называемые легковесные потоки. Они очень похожи на наши потоки, которые мы рассматривали на предыдущей лекции, но внутри они устроены более легковесно, и переключение между ними не требует почти никаких ресурсов операционной системы.\n", + "\n", + "Также один из известных фреймворков — это `Tornado`. Он был основан на `api` генераторов Python и устроен немного иначе, чем фреймворк `Gevent`.\n", + "\n", + "После Tornado в Python3 появился фреймворк `AsyncIO`, и он сейчас является мейнстримом. В его основе, в принципе, лежит то же самое, что и в `Tornado`, но тем не менее `AsyncIO` поставляется вместе с Python3 Core, он также основан на работе генераторов. Его поддерживают официальные сообщества, и поэтому все наши дальнейшие разговоры о программировании в один поток будут привязаны к фреймворку `AsyncIO`.\n", + "\n", + "Итак, мы обсудили, как работает неблокирующий ввод-вывод, иначе его еще называют мультиплексированием. Такой подход иначе называют мультиплексированием ввода-вывода.\n", + "\n", + "Мы рассмотрели пример простой программы, как использовать объект `select.epoll`, а также немного обсудили популярные фреймворки. И в дальнейших лекциях мы продолжим изучать работу `AsyncIO` для того, чтобы разобраться с работой кода в один поток." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Итераторы и генераторы, в чём разница%3F.ipynb b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Итераторы и генераторы, в чём разница%3F.ipynb new file mode 100644 index 0000000..221da33 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Итераторы и генераторы, в чём разница%3F.ipynb @@ -0,0 +1,268 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f5f73fc7", + "metadata": {}, + "source": [ + "# Итераторы и генераторы, в чём разница? #" + ] + }, + { + "cell_type": "markdown", + "id": "829f614a", + "metadata": {}, + "source": [ + "На этой лекции мы рассмотрим то, как устроены итераторы и генераторы. А самое главное, мы выясним их сходства и различия. Нам потребуются знания о том, как устроены генераторы для того, чтобы в дальнейшем понимать, как работают сопрограммы и как устроен фрэймворк `asyncio`.\n", + "\n", + "Итак, давайте рассмотрим пример с итератором." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1c2c0ee2", + "metadata": {}, + "outputs": [], + "source": [ + "# Итераторы\n", + "class MyRangeIterator:\n", + " def __init__(self, top):\n", + " self.top = top\n", + " self.current = 0\n", + " \n", + " def __iter__(self):\n", + " return self\n", + " \n", + " def __next__(self):\n", + " if self.current >= self.top:\n", + " raise StopIteration\n", + " \n", + " current = self.current\n", + " self.current += 1\n", + " \n", + " return current" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "419086db", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.MyRangeIterator at 0x7fa903e11a90>" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "counter = MyRangeIterator(3)\n", + "counter" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9659b408", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n" + ] + } + ], + "source": [ + "for it in counter:\n", + " print(it)" + ] + }, + { + "cell_type": "markdown", + "id": "15295206", + "metadata": {}, + "source": [ + "Итераторы могут применяться для генерации каких-либо последовательностей. Давайте рассмотрим пример, который приведён на слайде. Здесь мы генерируем последовательность 0, 1, 2. Для того, чтобы решить такую задачу при помощи итераторов, нам необходимо создать класс, в данном случае `MyRangeIterator`, а также переопределить у него `__init__` и методы `__iter__` и `__next__`.\n", + "\n", + "Итак, если мы создадим объект итератора и передадим его в цикл `for`, то в цикле `for` будет последовательно, то вызовется метод `__iter__`, а затем последовательно будет вызываться метод `__next__`.\n", + "\n", + "Давайте запустим консоль, отладчик и посмотрим, как этот код будет выполняться. Так, нам потребуется наш пример. Это наш итератор. Давайте запустим отладчик. Используем команду python3, указываем флаг `–m pdb` и запускаем наш код." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3fc740e1", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> /home/mikhaylovaf/projects/python/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex2.py(2)()\n", + "-> class MyRangeIterator:\n", + "(Pdb) \n", + "--KeyboardInterrupt--\n", + "(Pdb) " + ] + } + ], + "source": [ + "! python -m pdb ex2.py" + ] + }, + { + "cell_type": "markdown", + "id": "cccf97a8", + "metadata": {}, + "source": [ + "Итак, объявили наш класс. Далее создаем объект класса `MyRangeIterator`, передаем туда значение \"3\" для генерации последовательности и видим, что вызвался наш метод `__init__`. Мы сохранили значение \"3\" в неком внутреннем свойстве класса `top` и запомнили `current`. Давайте вспомним эти строчки. Мы вернулись из нашего метода `__init__` и находимся в начале цикла. Можем посмотреть, что `counter` действительно является объектом `MyRangeIterator`. Итак, инициируем цикл. Цикл вызывает метод `__iter__`, в методе `__iter__` мы должны вернуть себя же. То есть, мы возвращаем `self`, затем в цикле последовательно, как я уже говорил, будет вызываться метод `__next__`. Проверяем условия. Условия не срабатывают. Хочу обратить внимание, что здесь мы создали некую локальную переменную `current` и ее потом будем возвращать. А также, мы сохранили некое значение во внутреннем свойстве нашего объекта. Это очень важно. Мы к этому еще вернемся. Так, сохранили значение. Вернули. Напечатали текущее значение нашей переменной. Получили ноль. То же самое будет с другими итерациями цикла, пока мы не дойдем до завершения работы нашего итератора. Итак, выполнилось условие для выхода. Сгенерировалось исключение `StopIteration`. И наш итератор прекратил работу.\n", + "\n", + "Итак, запомним ключевые моменты работы итератора. В итераторе вызывается метод `__iter__` один раз, и на каждой итерации вызывается метод `__next__`. Для завершения мы используем генерацию исключения `StopIteration`, и самое главное на каждой итерации цикла, мы сохраняем некоторое значение, которое нам потребуется для генерации следующей последовательности в объекте итераторе." + ] + }, + { + "cell_type": "markdown", + "id": "d0c328f2", + "metadata": {}, + "source": [ + "Давайте рассмотрим, как можно решить такую же задачу при помощи генераторов. Как вы уже знаете, для того, чтобы объявить генератор, необходимо написать обычную функцию, назовем ее `my_range_generator`. Точно так же, как мы назвали класс." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "21720586", + "metadata": {}, + "outputs": [], + "source": [ + "# Генераторы\n", + "def my_range_generator(top):\n", + " current = 0\n", + " while current < top:\n", + " yield current\n", + " current += 1" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "5d8d7600", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "counter = my_range_generator(3)\n", + "counter" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "5075abde", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n" + ] + } + ], + "source": [ + "for it in counter:\n", + " print(it)" + ] + }, + { + "cell_type": "markdown", + "id": "1269323e", + "metadata": {}, + "source": [ + "И для того, чтобы эта функция стала генератором, необходимо в ней объявить ключевое слово `yield`. Всё, после того, как в функции есть `yield`, она становится генератором. Если мы вызовем эту функцию, то на самом деле вызова не произойдет, а создастся объект. И для того, чтобы вызвать код этой функции необходимо по этому объекту проитерироваться. Давайте точно так же запустим отладчик и посмотрим как этот код будет выполняться в отладчике." + ] + }, + { + "cell_type": "markdown", + "id": "186b48e8", + "metadata": {}, + "source": [ + "Завершим предыдущий пример. И запустим пример с генератором. Точно также используем флаг `-m pdb`. Наш пример." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de9cb81d", + "metadata": {}, + "outputs": [], + "source": [ + "! python -m pdb ex3.py" + ] + }, + { + "cell_type": "markdown", + "id": "9da18c98", + "metadata": {}, + "source": [ + "Итак мы объявили функцию. Обратите внимание, что сейчас выполнилась строчка и не произошло вызова функции. Так как функция является генератором, то вызов такой создаёт просто объект типа генератор.\n", + "\n", + "Если мы его попробуем напечатать, то действительно, это объект - генератор. После этого наш цикл `for` будет вызывать метод `next` для нашего генератора, и он начнёт свое исполнение. Давайте продолжим выполнение нашего генератора. Как мы видим, выполнилась функция, мы в нее зашли, зашли в наш цикл и дошли до первой инструкции `yield`. После того, как мы дошли до инструкции `yield`, наша функция вернула значение в наш цикл. И теперь уже мы в цикле сможем напечатать то значение, которое вернул нам наш генератор.\n", + "\n", + "Давайте немного остановимся на том, как произошел возврат из нашей функции. На самом деле, генератор как бы заморозил свою функцию и сохранил значения своего фрейма стека в объекте `counter`, или в объекте-генераторе. После того, как наша функция продолжит свое выполнение, стек восстановится, восстановится значение всех локальных переменных. В данном случае у нас есть переменные `current`, у нас есть локальные переменные `top`, значения их восстановятся, и цикл продолжится с того места, где у нас был вызов `yield`. Давайте проверим это.\n", + "\n", + "Итак мы напечатали значение ноль. Перешли снова в нашу функцию, восстановился стек, и функция продолжила выполнение с того же места, где она была как бы заморожена. Дальше происходит всё то же самое. До тех пор, пока наша функция не завершится. Давайте посмотрим. Все, наша функция завершилась. Итак, как мы видим, ключевое отличие здесь по сравнению с итераторами то, что нам не надо объявлять никакой класс. Мы объявили буквально функцию, также второе отличие - нам не нужно сохранять никаких значений, или глобальных состояний в объектах типа генератор, или типа итератор. Мы используем обычные локальные переменные, и это очень удобно.\n", + "\n", + "Подводя итоги, можно сказать, что в генераторы заложены очень большие возможности для написания `concurrency` кода. И в следующих лекциях мы рассмотрим, как работают корутины и разберемся в деталях их работы." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Клиент для отправки метрик.ipynb b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Клиент для отправки метрик.ipynb new file mode 100644 index 0000000..5d1c8da --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Клиент для отправки метрик.ipynb @@ -0,0 +1,309 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6bc66b73", + "metadata": {}, + "source": [ + "# Клиент для отправки метрик #" + ] + }, + { + "cell_type": "markdown", + "id": "454171d9", + "metadata": {}, + "source": [ + "## Хранение метрик ##\n", + "\n", + "Если вы разрабатываете настоящий проект, у которого есть большое количество пользователей, то необходимо наблюдать за всеми процессами, происходящими в нем. Для этого нужно смотреть за численными показателями в проекте. Показатели могут быть самыми разными - количество запросов к вашему приложению, время ответа вашего сервиса на каждый запрос, количество пользователей в сутки, и т.д. Эти всевозможные численные показатели мы будем называть метриками.\n", + "\n", + "Для сбора, хранения и отображения подобных метрик существуют готовые решения, например `Graphite`, `InfluxDB`. Мы в рамках курса разработаем свою систему для сбора метрик - сервер и клиент.\n", + "\n", + "В этом блоке мы начнем с разработки клиента для отправки подобных метрик на сервер, где они хранятся, и могут быть запрошены в любой момент времени. Затем в качестве финального задания в шестом блоке вам будет предложено реализовать и сам сервер." + ] + }, + { + "cell_type": "markdown", + "id": "08938207", + "metadata": {}, + "source": [ + "## Протокол взаимодействия ##\n", + "\n", + "Итак, в этом блоке вам необходимо разработать сетевую программу-клиент, при помощи которой можно отправлять различные метрики на сервер. Клиент и сервер должны взаимодействовать между собой по простому текстовому протоколу через TCP сокеты. Текстовый протокол имеет главное преимущество – он наглядный – можно просмотреть диалог взаимодействия клиентской и серверной стороны без использования дополнительных инструментов." + ] + }, + { + "cell_type": "markdown", + "id": "77376f2a", + "metadata": {}, + "source": [ + "Прежде чем реализовывать клиентское приложение давайте рассмотрим взаимодействие между клиентом и сервером на конкретных примерах." + ] + }, + { + "cell_type": "markdown", + "id": "80bace3a", + "metadata": {}, + "source": [ + "Предположим, необходимо собирать метрики о работе операционной системы: `cpu` (загрузка процессора), `memory usage` (потребление памяти), `disk usage` (потребление места на жестком диске), `network usage` (статистика сетевых интерфейсов) и т.д. Это понадобится для контроля загрузки серверов и прогноза по расширению парка железа компании - проще говоря для мониторинга.\n", + "\n", + "Пусть у нас имеется в наличии два сервера `huginn` и `muninn`. Мы будем получать загрузку центрального процессора на сервере и отправлять метрику с названием `имя_сервера.cpu`\n", + "\n", + "```\n", + "client -> server: put huginn.cpu 10.6 1642667947\\n\n", + "server -> client: ok\\n\\n\n", + "client -> server: put muninn.cpu 15.3 1642667959\\n\n", + "server -> client: ok\\n\\n\n", + "```\n", + "\n", + "Чтобы отправить метрику на сервер, вы отправляете в TCP-соединение строку вида:\n", + "\n", + "```\n", + "put huginn.cpu 10.6 1642667947\\n\n", + "```\n", + "\n", + "Ключевое слово `put` означает команду отправки метрики. За ней через пробел следует название (имя) самой метрики, например `huginn.cpu`, далее опять через пробел значение метрики, и через еще один пробел временная метка `unix timestamp`. Таким образом, во время `1642667947` значение метрики `huginn.cpu` было равно `10.6`. Наконец, команда заканчивается символом переноса строки `\\n`.\n", + "\n", + "В ответ на эту команду `put` сервер присылает уведомление об успешном сохранении метрики в виде строки:\n", + "\n", + "```\n", + "ok\\n\\n\n", + "```\n", + "\n", + "Два переноса строки в данном случае означают маркер конца сообщения от сервера клиенту." + ] + }, + { + "cell_type": "markdown", + "id": "a090a6c9", + "metadata": {}, + "source": [ + "## Команды ##\n", + "\n", + "Необходимо реализовать две команды:\n", + "\n", + "`put` - для сохранения метрик на сервере.\n", + "\n", + "`get` - для получения метрик.\n", + "\n", + "Формат команды `put` для отправки метрик — это строка вида:\n", + "\n", + "```\n", + "put \\n\n", + "```\n", + "\n", + "Успешный ответ от сервера:\n", + "\n", + "```\n", + "ok\\n\\n\n", + "```\n", + "\n", + "Ошибка сервера:\n", + "\n", + "```\n", + "error\\nwrong command\\n\\n\n", + "```\n", + "\n", + "Обратите внимание на то, что за каждым ответом сервера указано два символа `\\n`. В качестве значения метрики `value` используется вещественное число.\n", + "\n", + "Данные нужно не только отправлять на сервер, но и запрашивать их. Это может потребоваться для визуализации и анализа нужных метрик в определенные промежутки времени.\n", + "\n", + "Формат команды `get` для получения метрик — это строка вида:\n", + "\n", + "```\n", + "get \\n\n", + "```\n", + "\n", + "В качестве ключа можно указывать символ `*`, для этого символа будут возвращены все доступные метрики. В данном задании мы никак не ограничиваем количество метрик, которые должен вернуть сервер – сервер должен возвращать все метрики, удовлетворяющие ключу.\n", + "\n", + "Успешный ответ от сервера:\n", + "\n", + "```\n", + "ok\\nhuginn.cpu 10.5 1642667947\\nmuninn.cpu 15.3 1642667959\\n\\n\n", + "```\n", + "\n", + "Если ни одна метрика не удовлетворяет условиям поиска, то вернется ответ:\n", + "\n", + "```\n", + "ok\\n\\n\n", + "```\n", + "\n", + "Обратите внимание, что каждая успешная операция начинается с `\"ok\"`, а за ответом сервера всегда указано два символа `\\n`." + ] + }, + { + "cell_type": "markdown", + "id": "f37c929b", + "metadata": {}, + "source": [ + "## Реализация клиента. ##\n", + "\n", + "Необходимо реализовать класс `Client`, в котором будет инкапсулировано соединение с сервером, клиентский сокет и методы для получения и отправки метрик на сервер. В конструктор класса `Client` должна передаваться адресная пара хост и порт, а также необязательный аргумент `timeout` (`timeout=None` по умолчанию). У класса `Client` должно быть 2 метода: `put` и `get`, соответствующих протоколу выше.\n", + "\n", + "Пример вызова клиента для отправки метрик и затем их получения:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6f42bcf1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'huginn.cpu': [(1642667947, 0.5), (1642667948, 2.0), (1642667948, 0.5)], 'muninn.cpu': [(1642667950, 3.0), (1642667951, 4.0)], 'muninn.memory': [(1642748845, 4200000.0)]}\n" + ] + } + ], + "source": [ + "from client import Client\n", + "\n", + "client = Client(\"127.0.0.1\", 8888, timeout=15)\n", + "\n", + "client.put(\"huginn.cpu\", 0.5, timestamp=1642667947)\n", + "client.put(\"huginn.cpu\", 2.0, timestamp=1642667948)\n", + "client.put(\"huginn.cpu\", 0.5, timestamp=1642667948)\n", + "\n", + "client.put(\"muninn.cpu\", 3, timestamp=1642667950)\n", + "client.put(\"muninn.cpu\", 4, timestamp=1642667951)\n", + "client.put(\"muninn.memory\", 4200000)\n", + "\n", + "print(client.get(\"*\"))" + ] + }, + { + "cell_type": "markdown", + "id": "4acccf13", + "metadata": {}, + "source": [ + "Клиент получает данные в текстовом виде, метод `get` должен возвращать словарь с полученными ключами с сервера. Значением ключа в словаре является список кортежей `[(timestamp, metric_value), ...]`, отсортированный по `timestamp` от меньшего к большему. Значение timestamp должно быть преобразовано к целому числу `int`. Значение метрики `metric_value` нужно преобразовать к числу с плавающей точкой `float`.\n", + "\n", + "Метод `put` принимает первым аргументом название метрики, вторым численное значение, третьим - необязательный именованный аргумент `timestamp`. Если пользователь вызвал метод `put` без аргумента `timestamp`, то клиент автоматически должен подставить текущее время в команду `put - int(time.time())`\n", + "\n", + "Метод `put` не возвращает ничего в случае успешной отправки и выбрасывает исключение `ClientError` в случае неуспешной.\n", + "\n", + "Метод `get` принимает первым аргументом имя метрики, значения которой мы хотим выгрузить. Также вместо имени метрики можно использовать символ `*`, о котором говорилось в описании протокола.\n", + "\n", + "Метод `get` возвращает словарь с метриками (смотрите ниже пример) в случае успешного получения ответа от сервера и выбрасывает исключение `ClientError` в случае неуспешного.\n", + "\n", + "Пример возвращаемого значения при успешном вызове `client.get(\"huginn.cpu\")`:\n", + "\n", + "```python\n", + "{\n", + " 'huginn.cpu': [\n", + " (1642667947, 0.5),\n", + " (1642667948, 0.5)\n", + " ]\n", + "}\n", + "```\n", + "\n", + "Пример возвращаемого значения при успешном вызове `client.get(\"*\")`:\n", + "\n", + "```python\n", + "{\n", + " 'huginn.cpu': [\n", + " (1642667947, 0.5),\n", + " (1642667948, 0.5)\n", + " ],\n", + " 'muninn.cpu': [\n", + " (1642667950, 3.0),\n", + " (1642667951, 4.0)\n", + " ],\n", + " 'muninn.memory': [\n", + " (1642668557, 4200000.0)\n", + " ]\n", + "}\n", + "```\n", + "\n", + "Если в ответ на get-запрос сервер вернул положительный ответ `ok\\n\\n`, но без данных (то есть данных по запрашиваемому ключу нет), то метод `get` клиентадолжен вернуть пустой словарь:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c83223c7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{}\n" + ] + } + ], + "source": [ + "print(client.get(\"non_existing_key\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cc53e7c0", + "metadata": {}, + "source": [ + "Обратите внимание, что сервер хранит данные с максимальным разрешением в одну секунду. Это означает, что если в одну и ту же секунду отправить две одинаковые метрики, то будет сохранено только одно значение, которое было обработано последним. Все остальные значения будут перезаписаны.\n", + "\n", + "Итак, вам необходимо предоставить модуль с классом `Client`, исключением `ClientError`. В этом классе `Client` должны быть доступны методы `get` и `put` с описанной выше сигнатурой. При вызове методов `get` и `put` клиент должен посылать сообщения в TCP-соединение с сервером в соответствии с описанным текстовым протоколом, получать ответ от сервера, преобразовывать его в удобный для использования формат, описанный выше.\n", + "\n", + "Код клиента неудобно разрабатывать и отлаживать без сервера. Для удобства тестирования во время разработки кода клиента мы разработали `unittest`-ты.\n", + "\n", + "[test_client.py](test_client.py \"test_client.py\")\n", + "\n", + "Используйте данный `unittest` для проверки работы Вашего клиента для отправки метрик. Это ускорит процесс разработки клиента и упростит отладку." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "91854869", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".....\r\n", + "----------------------------------------------------------------------\r\n", + "Ran 5 tests in 0.001s\r\n", + "\r\n", + "OK\r\n" + ] + } + ], + "source": [ + "! python -m unittest test_client.py" + ] + }, + { + "cell_type": "markdown", + "id": "62e177a7", + "metadata": {}, + "source": [ + "Успехов при выполнении задания!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Первые шаги с asyncio.ipynb b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Первые шаги с asyncio.ipynb new file mode 100644 index 0000000..f94b9a3 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Первые шаги с asyncio.ipynb @@ -0,0 +1,310 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d26cab23", + "metadata": {}, + "source": [ + "# Первые шаги с asyncio #" + ] + }, + { + "cell_type": "markdown", + "id": "116dd167", + "metadata": {}, + "source": [ + "На этой лекции мы начнем свое первое знакомство с фреймворком `asyncio`, и мы разберем некоторые примеры кода с использованием этого фреймворка.\n", + "\n", + "Прежде чем мы начнем разбирать примеры кода на `asyncio`, хочется про него сказать следующее.\n", + "\n", + "Во-первых, `asyncio` — это библиотека, которая стала частью Python 3, и она распространяется вместе с самим дистрибутивом Python 3. `аsyncio` отвечает за неблокирующий ввод/вывод, на этом фреймворке можно написать сервис, который работает с десятками тысяч соединений одновременно. В основе работы этого фреймворка лежат генераторы и корутины, о чем мы уже говорили на предыдущих лекциях. И знания о том, как работают генераторы и корутины, помогут вам разобраться и с тем, как устроен фреймворк `asyncio`. В целом, это линейный код, в нем отсутствуют какие-либо `callback`-и. Выглядит он достаточно просто, но внутри устроен не совсем так легко, как кажется.\n", + "\n", + "Итак, давайте приступим к примерам. На слайде показан пример, в нем мы объявляем функцию, добавляем к этой функции декоратор `asyncio.coroutine`, тем самым делая нашу функцию корутиной." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e223ec44", + "metadata": {}, + "outputs": [], + "source": [ + "# asyncio, Hello World\n", + "import asyncio\n", + "\n", + "@asyncio.coroutine\n", + "def hello_world():\n", + " while True:\n", + " print(\"Hello World!\")\n", + " yield from asyncio.sleep(1.0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2cea80c1", + "metadata": {}, + "outputs": [], + "source": [ + "loop = asyncio.get_event_loop()\n", + "loop.run_until_complete(hello_world())\n", + "loop.close()" + ] + }, + { + "cell_type": "markdown", + "id": "dcb391dc", + "metadata": {}, + "source": [ + " Далее, мы в бесконечном цикле выполняем вывод строчки \"Hello World\" в консоль и делаем вызов `yield from asyncio.sleep`. Тем самым мы засыпаем на одну секунду. Хочу также обратить ваше внимание, что как раз мы здесь используем не привычный нам `time.sleep` вызов, а именно специальную конструкцию `yield from` для того, чтобы наша корутина приостановила свою работу, тем самым дала возможность поисполняться другим корутинам.\n", + " \n", + " Весь код в `asyncio` строится на основе понятия цикла обработки событий или, как еще его иногда называют, `event loop`. `event loop` — это своего рода планировщик задач или корутин, которые в нем исполняются. Он отвечает за ввод/вывод, он отвечает за управление сигналами, всеми сетевыми операциями и переключает контекст между всеми корутинами, которые в нем зарегистрированы и выполняются. Если одна корутина ожидает завершения какой-то сетевой операции, например, ждет, пока данные поступят в сокет, то в этот момент `event loop` может переключиться на другую корутину и продолжить ее выполнение.\n", + " \n", + "Давайте посмотрим далее в пример. При помощи вызова `asyncio.get_event_loop` мы получаем наш цикл обработки событий. Это объект, и мы можем попросить этот объект исполнить нам некоторую корутину. Опять же, хочу обратить ваше внимание, что обычные функции исполнять нельзя, нужно исполнять именно корутины. Давайте посмотрим, как этот пример работает в консоли. Нам потребуется код этого примера. Давайте запустим его при помощи интерпретатора python3. Запускаем." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e93cbbd0", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello World!\n", + "Hello World!\n", + "Hello World!\n", + "^C\n", + "Traceback (most recent call last):\n", + " File \"ex5.py\", line 11, in \n", + " loop.run_until_complete(hello_world())\n", + " File \"/usr/lib64/python3.6/asyncio/base_events.py\", line 471, in run_until_complete\n", + " self.run_forever()\n", + " File \"/usr/lib64/python3.6/asyncio/base_events.py\", line 438, in run_forever\n", + " self._run_once()\n", + " File \"/usr/lib64/python3.6/asyncio/base_events.py\", line 1415, in _run_once\n", + " event_list = self._selector.select(timeout)\n", + " File \"/usr/lib64/python3.6/selectors.py\", line 445, in select\n", + " fd_event_list = self._epoll.poll(timeout, max_ev)\n", + "KeyboardInterrupt\n" + ] + } + ], + "source": [ + "! python ex5.py" + ] + }, + { + "cell_type": "markdown", + "id": "77084278", + "metadata": {}, + "source": [ + "Видим на экране ожидаемое поведение, видим вывод строчки \"Hello World\". Для того, чтобы завершить работу с нашим циклом обработки событий, необходимо вызвать метод `close` для этого объекта `loop`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24532e1d", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# asyncio, async def / await; PEP 492 Python3.5\n", + "import asyncio\n", + "\n", + "async def hello_world():\n", + " while True:\n", + " print(\"Hello World!\")\n", + " await asyncio.sleep(1.0)\n", + " \n", + "loop = asyncio.get_event_loop()\n", + "loop.run_until_complete(hello_world())\n", + "loop.close()" + ] + }, + { + "cell_type": "markdown", + "id": "a92d9a69", + "metadata": {}, + "source": [ + "Начиная с версии Python 3.5, появился новый `PEP 492`, в котором был введен специальный синтаксис для написания корутин. Это `async def` и `await`. Во-первых, этот синтаксис выглядит более лаконично и красиво по сравнению с предыдущим. Наш пример сейчас стал занимать меньше строчек, но также он содержит еще в себе и другие важные плюсы. Например, если мы будем рефакторить функцию, которая была написана в старом стиле, при помощи декоратора `asyncio.coroutine` и вызова `yield from`, и предположим, мы закомментируем или сотрем вызов конструкции `yield from`. В таком случае эта функция перестанет быть генератором, и у нас код перестанет работать ожидаемым образом. Объявление функции через конструкцию `async def` гарантирует нам, что эта функция является точно корутиной.\n", + "\n", + "Если мы используем этот синтаксис, то внутри мы не можем использовать конструкцию `yield from`, мы обязаны использовать вызов `await`. Давайте рассмотрим более сложный пример и напишем свой TCP сервер, который обрабатывает несколько входящих соединений одновременно." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1fd74bfb", + "metadata": {}, + "outputs": [], + "source": [ + "# asyncio, tcp сервер\n", + "import asyncio\n", + "\n", + "async def handle_echo(reader, writer):\n", + " data = await reader.read(1024)\n", + " message = data.decode()\n", + " addr = writer.get_extra_info(\"peername\")\n", + " print(f\"received {message} from {addr}\")\n", + " writer.close()\n", + " \n", + "loop = asyncio.get_event_loop()\n", + "coro = asyncio.start_server(handle_echo, \"127.0.0.1\", 10001, loop=loop)\n", + "server = loop.run_until_complete(coro)\n", + "\n", + "try:\n", + " loop.run_forever()\n", + " \n", + "except KeyboardInterrupt:\n", + " pass\n", + "\n", + "server.close()\n", + "\n", + "loop.run_until_complete(server.wait_closed())\n", + "loop.close()" + ] + }, + { + "cell_type": "markdown", + "id": "4e816066", + "metadata": {}, + "source": [ + "Мы уже описали подобный сервер, когда изучали процессы и потоки, и здесь все то же самое. Итак, мы получаем наш `event_loop`, делаем вызов `start_server`, передаем в этот вызов нашу корутину. В функции `start_server` мы должны еще передать параметры в виде хоста и порта, на котором мы будем слушать наше новое соединение. Далее мы запускаем установку этого соединения и делаем вызов `loop.run_forever`. Тем самым мы будем обрабатывать все входящие соединения, и после того, как мы заакцептили соединение, это все уже будет реализовано внутри кода `asyncio`, для каждого соединения будет создана отдельная корутина, и в этой корутине будет выполнена наша функция.\n", + "\n", + "Обратите внимание, какие удобные объекты для того, чтобы работать с нашим сокетом. Есть `reader`. При помощи конструкции `await reader`. Мы можем читать данные из нашего сокета, и также существует `writer`, если нам будет необходимо, мы сможем записывать данные в наш сокет. Давайте посмотрим, как этот пример работает в консоли. Так, нам понадобится код нашего сервера. Запускаем его при помощи команды python3." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d8e18ce1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "^C\r\n" + ] + } + ], + "source": [ + "! python ex6.py" + ] + }, + { + "cell_type": "markdown", + "id": "2174eced", + "metadata": {}, + "source": [ + "Все, наш сервер готов для того, чтобы клиенты к нему подключились и отправили какую-либо информацию. Давайте создадим клиента. Пока мы создадим привычного нам синхронного клиента при помощи модуля `socket`. Запускаем еще раз интерпретатор, импортируем модуль `socket`, создаем соединение при помощи вызова `socket.create_connection`. В `create_connection` мы должны указать адресную пару. Передаем 127.0.0.1 и порт 10001. Итак, наш клиент готов. Давайте сразу подключим второй клиент в дополнительной консоли. Снова запускаем интерпретатор, делаем все то же самое. Итак, у нас сейчас готов к выполнению второй клиент. Давайте попробуем отправить данные в сокет. Помним, что в сокет нужно отправлять байтовые строки. Давайте отправим строку `ping2`. \n", + "\n", + "Итак, успешно отправилось 5 байт, давайте взглянем на наш сервер. Наш сервер обработал запрос со второй консоли. Давайте попробуем отправить данные с первого клиента. Пусть это будет строка `ping1`. Отправили 5 байт, посмотрели на сервер, да, действительно сервер обработал запрос и от первого клиента." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "073cf750", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import socket\n", + "\n", + "sock = socket.create_connection((\"127.0.0.1\", 10001))\n", + "sock.send(b\"ping2\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9d84c941", + "metadata": {}, + "outputs": [], + "source": [ + "sock.close()" + ] + }, + { + "cell_type": "markdown", + "id": "df6c2a09", + "metadata": {}, + "source": [ + "Хочу обратить ваше внимание, насколько код получился простым. Во-первых, у нас нет создания процессов или потоков. Нам не нужно организовывать взаимодействие между этими процессами и потоками. Наш код работает в одном потоке. Он не захватывает `GIL`, нет проблемы с `GIL`. Код линейный, нет никаких `callback`-ов, все очень просто и понятно. Внутри этого кода заложена работа генераторов или корутин. То есть, когда приходит новый запрос, создается новый корутин и эти корутины исполняются последовательно, но, тем самым, мы смогли в одном потоке обработать несколько клиентов.\n", + "\n", + "Давайте рассмотрим код клиента, который тоже может быть асинхронным." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00377e0a", + "metadata": {}, + "outputs": [], + "source": [ + "# asyncio, tcp клиент\n", + "import asyncio\n", + "\n", + "async def tcp_echo_client(message, loop):\n", + " reader, writer = await asyncio.open_connection(\"127.0.0.1\", 10001, loop=loop)\n", + " print(\"send: %r\" % message)\n", + " writer.write(message.encode())\n", + " writer.close()\n", + " \n", + "loop = asyncio.get_event_loop()\n", + "message = \"hello World!\"\n", + "loop.run_until_complete(tcp_echo_client(message, loop))\n", + "loop.close()" + ] + }, + { + "cell_type": "markdown", + "id": "830ad749", + "metadata": {}, + "source": [ + "Делается это тоже достаточно просто, мы получаем наш `event loop`. Допустим, мы будем передавать какую-то строчку. Пусть это будет всем известный \"hello world\". Мы попросим в нашем `event loop` запустить нашу функцию, которая является корутиной `tcp_echo_client`, передадим туда параметры `message` и наш `event loop`. Далее, для того, чтобы создать соединение, мы должны вызвать метод `asyncio.open_connection`. Точно в этот вызов мы должны отправить адресную пару, куда мы делаем соединение, и вызов `await` вернет нам `reader` и `writer`. Это два объекта, при помощи которых можно взаимодействовать с нашим удаленным сервером. То есть, при помощи объекта `reader` можно читать данные с сервера, при помощи объекта `writer` можно записывать данные на наш сервер. Опять же, вы можете легко создать несколько таких асинхронных клиентов и одновременно выполнять запросы на разные сервера, при этом не делая никаких потоков или процессов. Это очень удобно, просто, и код получается достаточно производительным.\n", + "\n", + "Итак, на этой лекции мы сделали первые шаги для работы с библиотекой `asyncio`. Мы разобрали несколько простых примеров, остановили внимание на том, как они работают, а также разобрали пример работы сетевой программы с использованием библиотеки `asyncio`. На следующей лекции мы продолжим изучать то, как устроено `asyncio` более подробно." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Работа с asyncio.ipynb b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Работа с asyncio.ipynb new file mode 100644 index 0000000..3fff8c8 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Работа с asyncio.ipynb @@ -0,0 +1,411 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9d2f693b", + "metadata": {}, + "source": [ + "# Работа с asyncio #" + ] + }, + { + "cell_type": "markdown", + "id": "7501f8e8", + "metadata": {}, + "source": [ + "На этой лекции мы продолжим изучать фреймворк `asyncio`, и мы поговорим о самых важных кирпичиках, которые вы будете использовать для написания программ с использованием библиотеки `asyncio`.\n", + "\n", + "Мы обсудим на этой лекции, что такое `asyncio.Future`, поговорим о том, как создавать объекты типа `asyncio.Task`. Также мы рассмотрим проблему запуска синхронных функций в цикле обработки событий и немного обсудим библиотеки, которые существуют уже для работы с фреймворком `asyncio`.\n", + "\n", + "Давайте перейдем к примеру." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f5b9ac17", + "metadata": {}, + "outputs": [], + "source": [ + "### asyncio.Future, аналог concurrent.futures.Future\n", + "import asyncio\n", + "\n", + "async def slow_operation(future):\n", + " await asyncio.sleep(1)\n", + " future.set_result(\"Future is done!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "aaca92a4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + ":4>>" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "loop = asyncio.get_event_loop()\n", + "future = asyncio.Future()\n", + "asyncio.ensure_future(slow_operation(future))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd778f01", + "metadata": {}, + "outputs": [], + "source": [ + "loop.run_until_complete(future)\n", + "print(future.result())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f32706e", + "metadata": {}, + "outputs": [], + "source": [ + "loop.close()" + ] + }, + { + "cell_type": "markdown", + "id": "969c9a2f", + "metadata": {}, + "source": [ + "На слайде показан пример функции, которая называется `slow_operation` — это наша корутина, которую мы объявили. Мы в нее передаем некий объект `future`. `asyncio.Future` — это такой объект, который исполняется, и его выполнение еще не завершено. \n", + "\n", + "Этот объект, целиком и полностью, его интерфейс соответствует объекту `concurrent.futures.Future`. Мы разбирали пример работы с этим объектом, когда знакомились с потоками, и исполняли код при помощи `ThreadPoolExecutor` объекта. Давайте разберем, что делается в этом примере. Мы объявили некую функцию, передали в нее наш созданный объект `future`, выполнили `sleep` на одну секунду, и после этого при помощи `set_result` выставили результат в наш объект типа `future`.\n", + "\n", + "Обратите внимание - в основной программе мы создаем этот объект, далее мы создаем нашу корутину создаем нашу корутину при помощи `ensure_future`, а в основном цикле обработки событий мы ожидаем завершения нашего объекта `future`, не этой функции, которая называется `slow_operation`, а именно нашего объекта `future`.\n", + "\n", + "Таким образом, при помощи объектов класса `future` можно выстраивать цепочки не только из двух объектов, но и более сложные цепочки, и очень удобно дожидаться завершения выполнения всех объектов. `event loop asyncio` сам исполнит нужный код и вернет нам результаты.\n", + "\n", + "Давайте посмотрим, как работает пример в консоли. Переключимся в консоль. Для этого нам понадобится наш пример. Так он выглядит. Давайте запустим его при помощи команды python3." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d3c131b7", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Future is done!\r\n" + ] + } + ], + "source": [ + "! python ex8.py" + ] + }, + { + "cell_type": "markdown", + "id": "d5e61af6", + "metadata": {}, + "source": [ + "Как мы видим, наша функция успешно отработала, и мы дождались выполнения нашей созданной `future`, или нашего созданного объекта класса `Future`.\n", + "\n", + "Давайте рассмотрим следующий пример и посмотрим, как можно запустить несколько корутин в одном `event loop`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de398fa0", + "metadata": {}, + "outputs": [], + "source": [ + "### asyncio.Task, запуск нескольких корутин\n", + "import asyncio\n", + "\n", + "async def sleep_task(num):\n", + " for i in range(5):\n", + " print(f\"process task: {num} iter: {i}\")\n", + " await asyncio.sleep(1)\n", + " \n", + " return num\n", + "\n", + "# ensure_future or create_task" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6052cfbb", + "metadata": {}, + "outputs": [], + "source": [ + "loop = asyncio.get_event_loop()\n", + "task_list = [loop.create_task(sleep_task(i)) for i in range(2)]\n", + "loop.run_until_complete(asyncio.wait(task_list))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c519ebb", + "metadata": {}, + "outputs": [], + "source": [ + "loop.run_until_complete(loop.create_task(sleep_task(3)))\n", + "loop.run_until_complete(asyncio.gather(\n", + " sleep_task(10),\n", + " sleep_task(20),\n", + "))" + ] + }, + { + "cell_type": "markdown", + "id": "e0c1da88", + "metadata": {}, + "source": [ + "Для этого, как правило, используется объект типа `asyncio.Task`. `asyncio.Task` является наследником класса `asyncio.Future`, и у него существует ряд дополнительных методов. Со всеми этими методами вы можете ознакомиться в документации, но мы рассмотрим лишь базовый принцип того, как создавать таски и как с ними работать.\n", + "\n", + "Итак, напрямую объект типа `asyncio.Task` создавать не нужно. Нужно использовать метод `create_task` из вашего `event loop` и передавать в него корутину. То есть, у каждого объекта типа `Task` есть собственная корутина, которую он внутри исполняет. \n", + "\n", + "Итак, мы создаем список из двух тасков. Запоминаем его в виде списка объектов, и далее при помощи метода `asyncio.wait` мы исполняем список наших тасков в нашем `event loop`.\n", + "\n", + "Обратите внимание, что можно исполнять как список тасков, так и отдельный таск. Например, если мы исполним код, который я сейчас выделил, то не нужно никаких дополнительных функций вида `asyncio.wait`. Также существует более удобная обертка для исполнения списка тасков — это `asyncio.gather`.\n", + "\n", + "Давайте рассмотрим, как этот пример будет работать на самом деле в консоли. Переключимся в консоль. Итак, нам нужен код. Давайте исполним его и посмотрим, как на самом деле работает наша корутина. Запускаем при помощи команды python3." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "3556b3f6", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "process task: 0 iter: 0\n", + "process task: 1 iter: 0\n", + "process task: 0 iter: 1\n", + "process task: 1 iter: 1\n", + "process task: 0 iter: 2\n", + "process task: 1 iter: 2\n", + "process task: 0 iter: 3\n", + "process task: 1 iter: 3\n", + "process task: 0 iter: 4\n", + "process task: 1 iter: 4\n", + "process task: 3 iter: 0\n", + "process task: 3 iter: 1\n", + "process task: 3 iter: 2\n", + "process task: 3 iter: 3\n", + "process task: 3 iter: 4\n", + "process task: 20 iter: 0\n", + "process task: 10 iter: 0\n", + "process task: 20 iter: 1\n", + "process task: 10 iter: 1\n", + "process task: 20 iter: 2\n", + "process task: 10 iter: 2\n", + "process task: 20 iter: 3\n", + "process task: 10 iter: 3\n", + "process task: 20 iter: 4\n", + "process task: 10 iter: 4\n" + ] + } + ], + "source": [ + "! python ex9.py" + ] + }, + { + "cell_type": "markdown", + "id": "d8e97341", + "metadata": {}, + "source": [ + "Обратите внимание на то, какой вывод сейчас у нас на экране присутствует. Хочу обратить ваше внимание, что у нас внутри корутины работают последовательно, но все эти корутины исполняются одновременно. Мы видим, что у нас сначала один таск выполняет нулевую итерацию, затем второй таск с номером 1 выполняет свою нулевую итерацию. И затем по очереди нулевой таск выполняет первую итерацию, первый таск выполняет первую итерацию, то есть наши функции, наши корутины, исполняются в `event loop`'е одновременно, а код при этом мы пишем последовательный.\n", + "\n", + "Это очень удобно, код выглядит достаточно просто. Он похож на исполнение потоков, но на самом деле он выполняется последовательно.\n", + "\n", + "Давайте еще раз переключимся в консоль и посмотрим на различные методы работы с тасками в `asyncio`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4df9ac92", + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio\n", + "\n", + "async def sleep_task(num):\n", + " for i in range(5):\n", + " print(f\"process task: {num} iter: {i}\")\n", + " await asyncio.sleep(1)\n", + " \n", + " return num\n", + "\n", + "loop = asyncio.get_event_loop()\n", + "\n", + "loop.run_until_complete(loop.create_task(sleep_task(3)))" + ] + }, + { + "cell_type": "markdown", + "id": "624350e4", + "metadata": {}, + "source": [ + "Так, нам еще раз понадобится наш пример. Запускаем интерпретатор python3. Давайте запустим отдельный таск в нашем `event loop`. Делается это достаточно просто - выполняем метод `loop.create_task`, тем самым создавая таск и добавляя его в наш `event loop`, и далее выполняем метод `run_until_complete`. Этот вызов должен нам вернуть результаты выполнения нашего таска.\n", + "\n", + "Да, действительно, мы видим результат выполнения нашего таска — это число 3.\n", + "\n", + "Давайте попробуем выполнить несколько тасков при помощи удобной функции `asyncio.gather`. Делается это точно так же — мы запускаем `loop.run_until_complete`, передаем туда удобную функцию `asyncio.gather` и в этой функции перечисляем список тасков.\n", + "\n", + "Хочу обратить внимание, в чем отличие. Нам не нужно здесь вызывать метод `create_task`, все внутри будет автоматически вызвано, не нужно запоминать список наших тасков. Вся эта конструкция вернет результаты выполнения. Давайте проверим. Итак, ожидаем завершения наших тасков." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab6ccef9", + "metadata": {}, + "outputs": [], + "source": [ + "rsp = loop.run_until_complete(asyncio.gather(sleep_task(4), sleep_task(5)))" + ] + }, + { + "cell_type": "markdown", + "id": "622fc023", + "metadata": {}, + "source": [ + "Мы опять видим, что таски хоть и работают последовательно, но запускаются они одновременно и пока один таск выполняет `sleep`, второй продолжает работать и так далее." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c4f6544", + "metadata": {}, + "outputs": [], + "source": [ + "rsp" + ] + }, + { + "cell_type": "markdown", + "id": "dfef2909", + "metadata": {}, + "source": [ + "Итак, мы получили ответ. И, действительно, это массив, который содержит результаты работы двух наших тасков. \n", + "\n", + "Давайте двигаться дальше, и мне хотелось бы остановить ваше внимание на том, как исполнить синхронную функцию в нашем `event loop`. Как правило, таких задач не должно возникать, но если вдруг они и возникли, то они будут представлять из себя небольшую сложность.\n", + "\n", + "Хочу объяснить, почему. Так как наш `event loop`, или цикл обработки событий, постоянно переключает контекст, и переключает контекст между всеми нашими корутинами и их исполняет последовательно, пока одна корутина ожидает ввода-вывода, вторую корутину наш `event loop` благополучно исполняет. Если код, который будет исполняться в корутине, будет блокирующим, то наш `event loop` не сможет делать переключения контекста, и это будет очень плохо сказываться на вообще всем нашем коде, и с этим нужно что-то делать.\n", + "\n", + "Как раз для этого в `asyncio` существует метод `run_in_executor`. Он означает запустить код буквально в пуле потоков, который внутри этого `event loop`'а автоматически будет создан. Можно использовать и собственный пул потоков, а можно использовать уже готовый по умолчанию. Более подробную информацию можно получить в документации, а на этом примере можно наблюдать, как функция `urlopen`, которая открывает некий `url`, который ей передали по `http`, скачивает результаты в отдельном потоке и мы дожидаемся результатов выполнения этой функции, опять же при помощи механизма `event loop`, который называется `futures`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a18a7bd", + "metadata": {}, + "outputs": [], + "source": [ + "# loop.run_in_executor, запуск в отдельном потоке\n", + "import asyncio\n", + "from urllib.request import urlopen\n", + "\n", + "# a synchronous function\n", + "def sync_get_url(url):\n", + " return urlopen(url).read()\n", + "\n", + "async def load_url(url, loop=None):\n", + " future = loop.run_in_executor(None, sync_get_url, url)\n", + " response = await future\n", + " print(len(response))\n", + " \n", + "loop = asyncio.get_event_loop()\n", + "loop.run_until_complete(load_url(\"http://vniitf.ru/\", loop=loop))" + ] + }, + { + "cell_type": "markdown", + "id": "09defb3d", + "metadata": {}, + "source": [ + "Давайте посмотрим, как он выполняется. Итак, мы вызываем метод `run_in_executor`. Здесь внутри будет создано нужное количество потоков, и наша функция `sync_get_url` с параметром `url` будет выполнена в отдельном потоке. Для того, чтобы дождаться результата выполнения нашей функции в отдельном потоке, мы используем конструкцию `await` и передаем туда объект `future`.\n", + "\n", + "Давайте посмотрим, как этот код будет работать в консоли. Итак, нам нужен код нашего примера. Давайте еще раз на него взглянем. Мы будем открывать страничку vniitf.ru и выводить на экран количество байт, которые она занимает. Итак, выполняем наш пример." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "356db6d9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "97427\r\n" + ] + } + ], + "source": [ + "! python ex10.py" + ] + }, + { + "cell_type": "markdown", + "id": "afe9e38e", + "metadata": {}, + "source": [ + "Мы видим на экране 95 килобайт. Мы исполнили сейчас функцию `urlopen` в отдельном потоке, и в нашем `event loop` мы дождались результатов выполнения этой функции, которая работала синхронно. Как правило, такие задачи будут возникать у вас, если в некоторой библиотеке не будет поддержки работы с `asyncio`. Это может сказаться негативно, как я уже сказал, на производительности, потому что, как мы помним, потоки в Python запускаются и работают с ограничением `GIL`, и будет лучше, если ваш код будет работать без этих вещей.\n", + "\n", + "Хочу остановить ваше внимание на том, что сейчас уже есть достаточно большое количество библиотек, которые работают с `asyncio`, где можно посмотреть документацию по ним и получить информацию, как их использовать. Я привел ссылку на слайде, это на `https://github.com/aio-libs`. Там существует достаточно большой список этих библиотек. Самая известная библиотека — это `aiohttp - https://github.com/aio-libs/aiohttp`. При помощи нее можно делать `http` запросы в вашем коде и выполнять эти запросы без `ThreadPoolExecutor`'а, а напрямую в корутинах. `aiomysql - https://github.com/aio-libs/aiomysql` — это библиотека для работы с популярной базой `mysql`, `aiomcache - https://github.com/aio-libs/aiomcache`, и так далее.\n", + "\n", + "Таких библиотек появляется в последнее время все больше и больше, и это радует, так как все это обеспечит хорошее качество написания кода для работы с библиотекой `asyncio`.\n", + "\n", + "Итак, на этой лекции мы рассмотрели основные приемы для написания кода с использованием библиотеки `asyncio`. Мы поговорили о том, что такое `asyncio.Future` объект. Мы поговорили о том, как можно запустить несколько объектов типа `asyncio.Task` и посмотреть, как они исполняются в `event loop`'е, а также обсудили проблему работы с синхронными функциями, какие приемы для этого использовать. Все эти подходы и проблемы, которые мы обсудили, понадобятся вам в дальнейшем для написания заданий и самостоятельного изучения библиотеки `asyncio`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Тест по блоку (Clear).ipynb b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Тест по блоку (Clear).ipynb new file mode 100644 index 0000000..2f5896a --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Тест по блоку (Clear).ipynb @@ -0,0 +1,218 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0d0851be", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "69cc4bb0", + "metadata": {}, + "source": [ + "##### Вас зовут:\n", + "___" + ] + }, + { + "cell_type": "markdown", + "id": "97a677e8", + "metadata": {}, + "source": [ + "##### 1. Системный вызов fork\n", + "\n", + "- [ ] используется для получения pid родительского процесса\n", + "- [ ] используется для вызова функций в отдельных потоках\n", + "- [ ] создает точную копию родительского процесса\n", + "- [ ] используется для создания дочернего процесса" + ] + }, + { + "cell_type": "markdown", + "id": "ed5a5645", + "metadata": {}, + "source": [ + "##### 2. Системный вызов fork\n", + "\n", + "- [ ] в дочернем процессе вызов fork возвращает 0\n", + "- [ ] в родительском процессе вызов fork возвращает 0\n", + "- [ ] в дочернем процессе вызов fork возвращает pid родительского процесса\n", + "- [ ] в родительском процессе вызов fork возвращает pid дочернего" + ] + }, + { + "cell_type": "markdown", + "id": "ed1ad7fd", + "metadata": {}, + "source": [ + "##### 3. Что происходит с памятью в дочернем процессе при работе с Python?\n", + "\n", + "- [ ] Копируется из родительского процесса\n", + "- [ ] Разделяется с родительским процессом" + ] + }, + { + "cell_type": "markdown", + "id": "089d9f0a", + "metadata": {}, + "source": [ + "##### 4. Файловые дескрипторы в дочернем процессе\n", + "\n", + "- [ ] будут недоступны, их нужно переоткрыть заново\n", + "- [ ] будут скопированы и переоткрыты\n", + "- [ ] будут скопированы\n", + "- [ ] будут переоткрыты Python интерпретатором автоматически" + ] + }, + { + "cell_type": "markdown", + "id": "c4795ad4", + "metadata": {}, + "source": [ + "##### 5. Выделить истинные выражения:\n", + "\n", + "- [ ] блокировки нужно брать в одном порядке, освобождать в произвольном\n", + "- [ ] предпочтительнее использовать контекстный менеджер для работы с блокировками в Python\n", + "- [ ] блокировки нужно брать в одном порядке, освобождать в обратном\n", + "- [ ] блокировки нужно брать в одном порядке, освобождать строго в том же порядке" + ] + }, + { + "cell_type": "markdown", + "id": "4136254c", + "metadata": {}, + "source": [ + "##### 6. Очереди замедляют процесс работы потоков Python, лучше использовать объекты блокировки\n", + "\n", + "- [ ] Да\n", + "- [ ] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "950359b9", + "metadata": {}, + "source": [ + "##### 7. Выделить истинные выражения:\n", + "\n", + "- [ ] GIL замедляет выполнение главного потока управления в Python3, даже если нет других потоков\n", + "- [ ] GIL никак не влияет на выполнение отдельных процессов на Python3\n", + "- [ ] GIL нужен для защиты памяти интерпретатора от разрушений\n", + "- [ ] GIL запрещает одновременное выполнение инструкций байткода в разных потоках" + ] + }, + { + "cell_type": "markdown", + "id": "dca2b7a0", + "metadata": {}, + "source": [ + "##### 8. Если программа на Python выполняет много операций ввода вывода (IO-bound), то для ускорения ее работы нужно\n", + "\n", + "- [ ] разбить ее на отдельные функции, выполнить их в потоках\n", + "- [ ] создать отдельные процессы" + ] + }, + { + "cell_type": "markdown", + "id": "470c17f9", + "metadata": {}, + "source": [ + "##### 9. Если программа на Python выполняет операции вычисления, требующие только CPU (CPU-bound), то для ускорения ее работы нужно\n", + "\n", + "- [ ] разбить ее на отдельные функции, выполнить их в потоках\n", + "- [ ] создать отдельные процессы" + ] + }, + { + "cell_type": "markdown", + "id": "db80a6b9", + "metadata": {}, + "source": [ + "##### 10. Генератор в Python – это\n", + "\n", + "- [ ] функция в которой присутствует инструкция `yield`\n", + "- [ ] инструкция `yield`\n", + "- [ ] специальный вызов `yield from`\n", + "- [ ] объект типа `concurrent.futures.Future`" + ] + }, + { + "cell_type": "markdown", + "id": "2dfd1a7f", + "metadata": {}, + "source": [ + "##### 11. Отметить все истинные утверждения:\n", + "\n", + "- [ ] итератор хранит значение для генерации следующего элемента последовательности в `self`\n", + "- [ ] генератор хранит значение для генерации следующего элемента последовательности в локальных переменных\n", + "- [ ] исключение `StopIteration` используется для остановки работы итератора\n", + "- [ ] итератор прерывает свое выполнение на каждой итерации, сохраняя состояние локальных переменных" + ] + }, + { + "cell_type": "markdown", + "id": "dbce1351", + "metadata": {}, + "source": [ + "##### 12. Отметить все истинные утверждения:\n", + "\n", + "- [ ] сопрограммы исполняются в отдельных потоках параллельно\n", + "- [ ] исключения нельзя обрабатывать в сопрограммах, это нужно делать в основном потоке\n", + "- [ ] сопрограмма может прерывать свою работу, сохраняя свое состояние и значения всех локальных переменных\n", + "- [ ] из одной сопрограммы можно вызвать другую при помощи `yield from`" + ] + }, + { + "cell_type": "markdown", + "id": "d0f7a809", + "metadata": {}, + "source": [ + "##### 13. Отметить все истинные утверждения, касающиеся `asyncio event loop`:\n", + "\n", + "- [ ] позволяет выполнять функции и `callable` Python объекты\n", + "- [ ] переключает контекст между Python-потоками\n", + "- [ ] выполняет все корутины последовательно, переключая контекст между ними\n", + "- [ ] выполняет несколько корутин параллельно в разных потоках\n", + "- [ ] отвечает за выделение памяти в основном процессе\n", + "- [ ] позволяет выполнять корутины, зарегистрированные в нем" + ] + }, + { + "cell_type": "markdown", + "id": "19f24d7a", + "metadata": {}, + "source": [ + "##### 14. Отметить все истинные утверждения:\n", + "\n", + "- [ ] блокирующий код в Python никак не повлияет на выполнение других корутин в `asyncio event loop`\n", + "- [ ] объект типа `asyncio.Task` выполняется в отдельном потоке управления, никак не влияя на выполнения других объектов типа `asyncio.Task`\n", + "- [ ] объект типа `asyncio.Task` хранит в себе связанную корутину и содержит статус ее выполнения\n", + "- [ ] из объектов типа `asyncio.Future` можно создавать цепочки и дожидаться их выполнения в `asyncio event loop`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Тест по блоку.ipynb b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Тест по блоку.ipynb new file mode 100644 index 0000000..28bbb9f --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/Тест по блоку.ipynb @@ -0,0 +1,209 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0d0851be", + "metadata": {}, + "source": [ + "# Тест по блоку #" + ] + }, + { + "cell_type": "markdown", + "id": "97a677e8", + "metadata": {}, + "source": [ + "1. Системный вызов fork\n", + "\n", + "- [ ] используется для получения pid родительского процесса\n", + "- [ ] используется для вызова функций в отдельных потоках\n", + "- [x] создает точную копию родительского процесса\n", + "- [x] используется для создания дочернего процесса" + ] + }, + { + "cell_type": "markdown", + "id": "ed5a5645", + "metadata": {}, + "source": [ + "2. Системный вызов fork\n", + "\n", + "- [x] в дочернем процессе вызов fork возвращает 0\n", + "- [ ] в родительском процессе вызов fork возвращает 0\n", + "- [ ] в дочернем процессе вызов fork возвращает pid родительского процесса\n", + "- [x] в родительском процессе вызов fork возвращает pid дочернего" + ] + }, + { + "cell_type": "markdown", + "id": "ed1ad7fd", + "metadata": {}, + "source": [ + "3. Что происходит с памятью в дочернем процессе при работе с Python?\n", + "\n", + "- [x] Копируется из родительского процесса\n", + "- [ ] Разделяется с родительским процессом" + ] + }, + { + "cell_type": "markdown", + "id": "089d9f0a", + "metadata": {}, + "source": [ + "4. Файловые дескрипторы в дочернем процессе\n", + "\n", + "- [ ] будут недоступны, их нужно переоткрыть заново\n", + "- [ ] будут скопированы и переоткрыты\n", + "- [x] будут скопированы\n", + "- [ ] будут переоткрыты Python интерпретатором автоматически" + ] + }, + { + "cell_type": "markdown", + "id": "c4795ad4", + "metadata": {}, + "source": [ + "5. Выделить истинные выражения:\n", + "\n", + "- [ ] блокировки нужно брать в одном порядке, освобождать в произвольном\n", + "- [x] предпочтительнее использовать контекстный менеджер для работы с блокировками в Python\n", + "- [x] блокировки нужно брать в одном порядке, освобождать в обратном\n", + "- [ ] блокировки нужно брать в одном порядке, освобождать строго в том же порядке" + ] + }, + { + "cell_type": "markdown", + "id": "4136254c", + "metadata": {}, + "source": [ + "6. Очереди замедляют процесс работы потоков Python, лучше использовать объекты блокировки\n", + "\n", + "- [ ] Да\n", + "- [x] Нет" + ] + }, + { + "cell_type": "markdown", + "id": "950359b9", + "metadata": {}, + "source": [ + "7. Выделить истинные выражения:\n", + "\n", + "- [ ] GIL замедляет выполнение главного потока управления в Python3, даже если нет других потоков\n", + "- [x] GIL никак не влияет на выполнение отдельных процессов на Python3\n", + "- [x] GIL нужен для защиты памяти интерпретатора от разрушений\n", + "- [x] GIL запрещает одновременное выполнение инструкций байткода в разных потоках" + ] + }, + { + "cell_type": "markdown", + "id": "dca2b7a0", + "metadata": {}, + "source": [ + "8. Если программа на Python выполняет много операций ввода вывода (IO-bound), то для ускорения ее работы нужно\n", + "\n", + "- [x] разбить ее на отдельные функции, выполнить их в потоках\n", + "- [ ] создать отдельные процессы" + ] + }, + { + "cell_type": "markdown", + "id": "470c17f9", + "metadata": {}, + "source": [ + "9. Если программа на Python выполняет операции вычисления, требующие только CPU (CPU-bound), то для ускорения ее работы нужно\n", + "\n", + "- [ ] разбить ее на отдельные функции, выполнить их в потоках\n", + "- [x] создать отдельные процессы" + ] + }, + { + "cell_type": "markdown", + "id": "db80a6b9", + "metadata": {}, + "source": [ + "10. Генератор в Python – это\n", + "\n", + "- [x] функция в которой присутствует инструкция `yield`\n", + "- [ ] инструкция `yield`\n", + "- [ ] специальный вызов `yield from`\n", + "- [ ] объект типа `concurrent.futures.Future`" + ] + }, + { + "cell_type": "markdown", + "id": "2dfd1a7f", + "metadata": {}, + "source": [ + "11. Отметить все истинные утверждения:\n", + "\n", + "- [x] итератор хранит значение для генерации следующего элемента последовательности в `self`\n", + "- [x] генератор хранит значение для генерации следующего элемента последовательности в локальных переменных\n", + "- [x] исключение `StopIteration` используется для остановки работы итератора\n", + "- [ ] итератор прерывает свое выполнение на каждой итерации, сохраняя состояние локальных переменных" + ] + }, + { + "cell_type": "markdown", + "id": "dbce1351", + "metadata": {}, + "source": [ + "12. Отметить все истинные утверждения:\n", + "\n", + "- [ ] сопрограммы исполняются в отдельных потоках параллельно\n", + "- [x] исключения нельзя обрабатывать в сопрограммах, это нужно делать в основном потоке\n", + "- [x] сопрограмма может прерывать свою работу, сохраняя свое состояние и значения всех локальных переменных\n", + "- [x] из одной сопрограммы можно вызвать другую при помощи `yield from`" + ] + }, + { + "cell_type": "markdown", + "id": "d0f7a809", + "metadata": {}, + "source": [ + "13. Отметить все истинные утверждения, касающиеся `asyncio event loop`:\n", + "\n", + "- [ ] позволяет выполнять функции и `callable` Python объекты\n", + "- [ ] переключает контекст между Python-потоками\n", + "- [x] выполняет все корутины последовательно, переключая контекст между ними\n", + "- [ ] выполняет несколько корутин параллельно в разных потоках\n", + "- [ ] отвечает за выделение памяти в основном процессе\n", + "- [x] позволяет выполнять корутины, зарегистрированные в нем" + ] + }, + { + "cell_type": "markdown", + "id": "19f24d7a", + "metadata": {}, + "source": [ + "14. Отметить все истинные утверждения:\n", + "\n", + "- [ ] блокирующий код в Python никак не повлияет на выполнение других корутин в `asyncio event loop`\n", + "- [ ] объект типа `asyncio.Task` выполняется в отдельном потоке управления, никак не влияя на выполнения других объектов типа `asyncio.Task`\n", + "- [x] объект типа `asyncio.Task` хранит в себе связанную корутину и содержит статус ее выполнения\n", + "- [x] из объектов типа `asyncio.Future` можно создавать цепочки и дожидаться их выполнения в `asyncio event loop`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/5. Многопоточное и асинхронное программирование/Readme.ipynb b/5. Многопоточное и асинхронное программирование/Readme.ipynb new file mode 100644 index 0000000..ea3dcc4 --- /dev/null +++ b/5. Многопоточное и асинхронное программирование/Readme.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "caba6fd4", + "metadata": {}, + "source": [ + "# Многопоточное и асинхронное программирование #\n", + "\n", + "Пятый блок посвящен одной из самых актуальных тем современного мира разработки — асинхронному и многопоточному программированию. Вы узнаете про процессы и потоки, а также научитесь писать асинхронный код с помощью `asyncio`." + ] + }, + { + "cell_type": "markdown", + "id": "cfec6f06", + "metadata": {}, + "source": [ + "## Задачи обучения ##\n", + "\n", + "- Научиться писать многопоточный код на Python.\n", + "- Научиться писать асинхронный код на Python.\n", + "- Научиться работать с сетью.\n", + "- Получить знания о процессах и потоках ОС." + ] + }, + { + "cell_type": "markdown", + "id": "ee68e033", + "metadata": {}, + "source": [ + "## Оглавление ##" + ] + }, + { + "cell_type": "markdown", + "id": "78fa6464", + "metadata": {}, + "source": [ + "### Процессы и потоки ###\n", + "\n", + "- [Процесс и его характеристики](1.%20Процессы%20и%20потоки/Процесс%20и%20его%20характеристики.ipynb)\n", + "- [Создание процессов](1.%20Процессы%20и%20потоки/Создание%20процессов.ipynb)\n", + "- [Создание потоков](1.%20Процессы%20и%20потоки/Создание%20потоков.ipynb)\n", + "- [Синхронизация потоков](1.%20Процессы%20и%20потоки/Синхронизация%20потоков.ipynb)\n", + "- [Глобальная блокировка интерпретатора](1.%20Процессы%20и%20потоки/Глобальная%20блокировка%20интерпретатора.ipynb)\n", + "- [Документация](1.%20Процессы%20и%20потоки/Документация.ipynb)\n", + "- [Тест по процессам и потокам](1.%20Процессы%20и%20потоки/Тест%20по%20процессам%20и%20потокам.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "4b69a184", + "metadata": {}, + "source": [ + "### Работа с сетью, сокеты ###\n", + "\n", + "- [Сокеты, клиент-сервер](2.%20Работа%20с%20сетью,%20сокеты/Сокеты,%20клиент-сервер.ipynb)\n", + "- [Таймауты и обработка сетевых ошибок](2.%20Работа%20с%20сетью,%20сокеты/Таймауты%20и%20обработка%20сетевых%20ошибок.ipynb)\n", + "- [Обработка нескольких соединений](2.%20Работа%20с%20сетью,%20сокеты/Обработка%20нескольких%20соединений.ipynb)\n", + "- [Документация](2.%20Работа%20с%20сетью,%20сокеты/Документация.ipynb)\n", + "- [Тест по работе с сетью и сокетами](2.%20Работа%20с%20сетью,%20сокеты/Тест%20по%20работе%20с%20сетью%20и%20сокетами.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "5ebb0f04", + "metadata": {}, + "source": [ + "### Асинхронное программирование ###\n", + " \t\n", + "- [Исполнение кода в одном потоке, модуль select](3.%20Асинхронное%20программирование/Исполнение%20кода%20в%20одном%20потоке,%20модуль%20select.ipynb)\n", + "- [Итераторы и генераторы, в чём разница?](3.%20Асинхронное%20программирование/Итераторы%20и%20генераторы,%20в%20чём%20разница%3F.ipynb)\n", + "- [Генераторы и сопрограммы](3.%20Асинхронное%20программирование/Генераторы%20и%20сопрограммы.ipynb)\n", + "- [Первые шаги с asyncio](3.%20Асинхронное%20программирование/Первые%20шаги%20с%20asyncio.ipynb)\n", + "- [Работа с asyncio](3.%20Асинхронное%20программирование/Работа%20с%20asyncio.ipynb)\n", + "- [Документация](3.%20Асинхронное%20программирование/Документация.ipynb)\n", + "- [Клиент для отправки метрик](3.%20Асинхронное%20программирование/Клиент%20для%20отправки%20метрик.ipynb)\n", + "- [Тест по блоку](3.%20Асинхронное%20программирование/Тест%20по%20блоку.ipynb)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/6. Финальный проект/1. Финальный проект/Untitled.ipynb b/6. Финальный проект/1. Финальный проект/Untitled.ipynb new file mode 100644 index 0000000..daab21f --- /dev/null +++ b/6. Финальный проект/1. Финальный проект/Untitled.ipynb @@ -0,0 +1,325 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 24, + "id": "4da64377", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mreformatted server.py\u001b[0m\r\n", + "\u001b[1mAll done! ✨ 🍰 ✨\u001b[0m\r\n", + "\u001b[1m1 file reformatted\u001b[0m.\r\n" + ] + } + ], + "source": [ + "! black -l 79 server.py" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "22c4a892", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0m" + ] + } + ], + "source": [ + "! isort server.py" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "2131e0fe", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "************* Module server\n", + "server.py:18:8: W0201: Attribute 'transport' defined outside __init__ (attribute-defined-outside-init)\n", + "\n", + "------------------------------------------------------------------\n", + "Your code has been rated at 9.80/10 (previous run: 9.79/10, +0.01)\n", + "\n", + "\u001b[0m" + ] + } + ], + "source": [ + "! pylint server.py" + ] + }, + { + "cell_type": "markdown", + "id": "d801e0c9", + "metadata": {}, + "source": [ + "___" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1378d92e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mAll done! ✨ 🍰 ✨\u001b[0m\r\n", + "1 file left unchanged.\r\n" + ] + } + ], + "source": [ + "! black -l 79 test.py" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ad4f204e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fixing /home/mikhaylovaf/projects/python/6. Финальный проект/1. Финальный проект/test.py\r\n", + "\u001b[0m" + ] + } + ], + "source": [ + "! isort test.py" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "3b7f2d6a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "************* Module test\n", + "test.py:7:71: C0303: Trailing whitespace (trailing-whitespace)\n", + "test.py:60:0: C0301: Line too long (112/100) (line-too-long)\n", + "test.py:74:0: C0301: Line too long (113/100) (line-too-long)\n", + "test.py:86:0: C0301: Line too long (128/100) (line-too-long)\n", + "test.py:17:0: C0116: Missing function or method docstring (missing-function-docstring)\n", + "test.py:47:11: W0703: Catching too general exception Exception (broad-except)\n", + "test.py:64:11: W0703: Catching too general exception Exception (broad-except)\n", + "test.py:78:11: W0703: Catching too general exception Exception (broad-except)\n", + "test.py:90:11: W0703: Catching too general exception Exception (broad-except)\n", + "test.py:84:11: C1803: 'result != {}' can be simplified to 'result' as an empty sequence is falsey (use-implicit-booleaness-not-comparison)\n", + "test.py:17:0: R0915: Too many statements (52/50) (too-many-statements)\n", + "\n", + "------------------------------------------------------------------\n", + "Your code has been rated at 7.96/10 (previous run: 4.11/10, +3.86)\n", + "\n", + "\u001b[0m" + ] + } + ], + "source": [ + "! pylint test.py" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "af6c56ab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Похоже, что все верно!\r\n" + ] + } + ], + "source": [ + "! python test.py" + ] + }, + { + "cell_type": "markdown", + "id": "553480a3", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "29ac8524", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mreformatted client.py\u001b[0m\r\n", + "\u001b[1mAll done! ✨ 🍰 ✨\u001b[0m\r\n", + "\u001b[1m1 file reformatted\u001b[0m.\r\n" + ] + } + ], + "source": [ + "! black -l 79 client.py" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "ce72925b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0m" + ] + } + ], + "source": [ + "! isort client.py" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "c24170d2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\r\n", + "--------------------------------------------------------------------\r\n", + "Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)\r\n", + "\r\n", + "\u001b[0m" + ] + } + ], + "source": [ + "! pylint client.py" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "994d9338", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".....\r\n", + "----------------------------------------------------------------------\r\n", + "Ran 5 tests in 0.001s\r\n", + "\r\n", + "OK\r\n" + ] + } + ], + "source": [ + "! python -m unittest test_client.py" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "31cfddc7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'huginn.cpu': [(1642667947, 0.5), (1642667948, 2.0), (1642667948, 0.5)], 'muninn.cpu': [(1642667950, 3.0), (1642667951, 4.0)], 'muninn.memory': [(1642751508, 4200000.0)]}\n" + ] + } + ], + "source": [ + "from client import Client\n", + "\n", + "client = Client(\"127.0.0.1\", 8888, timeout=15)\n", + "\n", + "client.put(\"huginn.cpu\", 0.5, timestamp=1642667947)\n", + "client.put(\"huginn.cpu\", 2.0, timestamp=1642667948)\n", + "client.put(\"huginn.cpu\", 0.5, timestamp=1642667948)\n", + "\n", + "client.put(\"muninn.cpu\", 3, timestamp=1642667950)\n", + "client.put(\"muninn.cpu\", 4, timestamp=1642667951)\n", + "client.put(\"muninn.memory\", 4200000)\n", + "\n", + "print(client.get(\"*\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "14aac259", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{}\n" + ] + } + ], + "source": [ + "print(client.get(\"non_existing_key\"))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/6. Финальный проект/1. Финальный проект/client.py b/6. Финальный проект/1. Финальный проект/client.py new file mode 100644 index 0000000..cf30c06 --- /dev/null +++ b/6. Финальный проект/1. Финальный проект/client.py @@ -0,0 +1,93 @@ +"""Реализация клиента для сервера метрик""" + +import socket +from time import time +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Dict, List, Optional, Tuple + + +class ClientError(Exception): + """Общий класс исключений клиента""" + + ... + + +class ClientSocketError(ClientError): + """Исключение, выбрасываемое клиентом при сетевой ошибке""" + + ... + + +class ClientProtocolError(ClientError): + """Исключение, выбрасываемое клиентом при ошибке протокола""" + + ... + + +class Client: + """Класс клиент для сервера метрик""" + + def __init__( + self, host: "str", port: "int", timeout: "Optional[int]" = None + ): + """Конструктор класса""" + + self.connection: "Tuple[str,int]" = (host, port) + self.timeout: "Optional[int]" = timeout + + def send(self, cmd: "str") -> "str": + """Отправка команд серверу""" + data: "bytes" = b"" + + try: + with socket.create_connection( + self.connection, self.timeout + ) as sock: + sock.sendall(cmd.encode("utf8")) + + while not data.endswith(b"\n\n"): + data += sock.recv(1024) + + except socket.error as err: + raise ClientSocketError("error create connection", err) from err + + status, payload = data.decode("utf-8").split("\n", 1) + payload: "str" = payload.strip() + + if status == "error": + raise ClientProtocolError(payload) + + return payload + + def put( + self, metric: "str", value: "float", timestamp: "Optional[int]" = None + ) -> "None": + """Метод отправки данных""" + + self.send( + f"put {metric} {value} {timestamp if timestamp else int(time())}\n" + ) + + def get(self, metric: "str") -> "Dict[str,List[Tuple[int,float]]]": + """Метод получения данных""" + + result: "Dict[str,List[Tuple[int,float]]]" = {} + + for line in self.send(f"get {metric}\n").splitlines(): + try: + _metric, value, timestamp = line.split() + + if not _metric in result: + result[_metric] = [] + + result[_metric].append((int(timestamp), float(value))) + + except ValueError as error: + raise ClientProtocolError(line) from error + + for item in result.items(): + item[1].sort(key=lambda stamp: stamp[0]) + + return result diff --git a/6. Финальный проект/1. Финальный проект/server.py b/6. Финальный проект/1. Финальный проект/server.py new file mode 100644 index 0000000..deede94 --- /dev/null +++ b/6. Финальный проект/1. Финальный проект/server.py @@ -0,0 +1,86 @@ +"""Реализация сервера для приема метрик""" + +from asyncio import Protocol, get_event_loop +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Dict, List, Tuple + +g_storage: "Dict[str,List[Tuple[int,float]]]" = {} + + +class ClientServerProtocol(Protocol): + """Реализация протокола""" + + def connection_made(self, transport): + """connection_made""" + + self.transport = transport + + def data_received(self, data): + """Обработка поступивших данных""" + + result: "str" = "error\nwrong command\n\n" + command: "str" = data.decode("utf-8").strip("\r\n") + + chunks: "List[str]" = command.split(" ") + + if chunks[0] == "get": + result = "ok\n" + + if chunks[1] == "*": + for key, values in g_storage.items(): + for value in values: + result += f"{key} {value[1]} {value[0]}\n" + + else: + if chunks[1] in g_storage: + for value in g_storage[chunks[1]]: + result += f"{chunks[1]} {value[1]} {value[0]}\n" + + result += "\n" + + elif chunks[0] == "put": + if chunks[1] == "*": + result = "error\nkey cannot contain *\n\n" + + else: + if not chunks[1] in g_storage: + g_storage[chunks[1]] = [] + + try: + timestamp: "int" = int(chunks[3]) + value: "float" = float(chunks[2]) + + if not (timestamp, value) in g_storage[chunks[1]]: + g_storage[chunks[1]].append((timestamp, value)) + g_storage[chunks[1]].sort(key=lambda item: item[0]) + + result = "ok\n\n" + + except ValueError: + result = "error\nvalue error\n\n" + + self.transport.write(result.encode("utf-8")) + + +def run_server(host: "str", port: "int") -> None: + """Запуск сервера""" + + loop = get_event_loop() + coro = loop.create_server(ClientServerProtocol, host, port) + server = loop.run_until_complete(coro) + + try: + loop.run_forever() + + except KeyboardInterrupt: + ... + + server.close() + loop.run_until_complete(server.wait_closed()) + loop.close() + + +if __name__ == "__main__": + run_server("127.0.0.1", 8888) diff --git a/6. Финальный проект/1. Финальный проект/test.py b/6. Финальный проект/1. Финальный проект/test.py new file mode 100644 index 0000000..7be3f8e --- /dev/null +++ b/6. Финальный проект/1. Финальный проект/test.py @@ -0,0 +1,100 @@ +""" +Это вспомогательный скрипт для тестирования сервера из задания в 6 блоке. + +Для запуска скрипта на локальном компьютере разместите рядом файл client.py, +где содержится код клиента, который реализован в задание блока 5. + +Сначала запускаете ваш сервер на адресе 127.0.0.1 и порту 8888, а затем +запускаете этот скрипт. + +""" + +import sys + +from client import Client, ClientProtocolError, ClientSocketError + + +def run(host, port): + + client1 = Client(host, port, timeout=5) + client2 = Client(host, port, timeout=5) + + try: + client1.send("malformed command test\n") + client2.send("malformed command test\n") + + except ClientSocketError as err: + print(f"Ошибка общения с сервером: {err.__class__}: {err}") + sys.exit(1) + + except ClientProtocolError: + pass + + else: + print( + "Неверная команда, отправленная серверу, должна возвращать ошибку протокола" + ) + sys.exit(1) + + try: + client1.put("k1", 0.25, timestamp=1) + client2.put("k1", 2.156, timestamp=2) + client1.put("k1", 0.35, timestamp=3) + client2.put("k2", 30, timestamp=4) + client1.put("k2", 40, timestamp=5) + client1.put("k2", 40, timestamp=5) + + except Exception as err: + print(f"Ошибка вызова client.put(...) {err.__class__}: {err}") + sys.exit(1) + + expected_metrics = { + "k1": [(1, 0.25), (2, 2.156), (3, 0.35)], + "k2": [(4, 30.0), (5, 40.0)], + } + + try: + metrics = client1.get("*") + if metrics != expected_metrics: + print( + f"client.get('*') вернул неверный результат. Ожидается: {expected_metrics}. Получено: {metrics}" + ) + sys.exit(1) + + except Exception as err: + print(f"Ошибка вызова client.get('*') {err.__class__}: {err}") + sys.exit(1) + + expected_metrics = {"k2": [(4, 30.0), (5, 40.0)]} + + try: + metrics = client2.get("k2") + if metrics != expected_metrics: + print( + f"client.get('k2') вернул неверный результат. Ожидается: {expected_metrics}. Получено: {metrics}" + ) + sys.exit(1) + + except Exception as err: + print(f"Ошибка вызова client.get('k2') {err.__class__}: {err}") + sys.exit(1) + + try: + result = client1.get("k3") + if result != {}: + print( + f"Ошибка вызова метода get с ключом, который еще не был добавлен. Ожидается: пустой словарь. Получено: {result}" + ) + sys.exit(1) + + except Exception as err: + print( + f"Ошибка вызова метода get с ключом, который еще не был добавлен: {err.__class__} {err}" + ) + sys.exit(1) + + print("Похоже, что все верно!") + + +if __name__ == "__main__": + run("127.0.0.1", 8888) diff --git a/6. Финальный проект/1. Финальный проект/test_client.py b/6. Финальный проект/1. Финальный проект/test_client.py new file mode 100644 index 0000000..477852d --- /dev/null +++ b/6. Финальный проект/1. Финальный проект/test_client.py @@ -0,0 +1,157 @@ +""" + Это unittest для тестирования вашего класса Client из задания. + + Для запуска теста на локальном компьютере разместите код unittest-та + и код решения в одном каталоге. Запустите тест при помощи команды: + + python -m unittest test_week5.py + + Обратите внимание на то, что ваш модуль должен называться client.py. + Это не обязательное требование, если вы назвали мобуль по-другому, то + просто измените его импорт в строке 25 на: + from you_module_name import Client, ClientError + + Модуль должен содержать классы Client и ClientError. + + Этот unittest поможет вам выполнить задание. + Успехов! +""" + +import unittest +from unittest.mock import patch +from collections import deque + +# импорт модуля с решением +from client import Client, ClientError + + +class ServerSocketException(Exception): + pass + + +class ServerSocket: + """Mock socket module""" + + def __init__(self): + self.response_buf = deque() + self.rsp_map = { + b'put test 0.5 1\n': b'ok\n\n', + b'put test 2.0 2\n': b'ok\n\n', + b'put test 0.4 2\n': b'ok\n\n', + b'put load 301 3\n': b'ok\n\n', + b'get key_not_exists\n': b'ok\n\n', + b'get test\n': b'ok\n' + b'test 0.5 1\n' + b'test 0.4 2\n\n', + b'get get_client_error\n': b'error\nwrong command\n\n', + b'get *\n': b'ok\n' + b'test 0.5 1\n' + b'test 0.4 2\n' + b'load 301 3\n\n', + } + + def sendall(self, data): + return self.send(data) + + def send(self, data): + if data in self.rsp_map: + self.response_buf.append(self.rsp_map[data]) + else: + raise ServerSocketException(f"запрос не соответствует протоколу: {data}") + + def recv(self, bytes_count): + try: + rsp = self.response_buf.popleft() + except IndexError: + raise ServerSocketException("нет данных в сокете для чтения ответа") + + return rsp + + @classmethod + def create_connection(cls, *args, **kwargs): + return cls() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + def __getattr__(self, feature): + """ignore socket.connect, soket.bind, etc...""" + pass + + +class TestClient(unittest.TestCase): + @classmethod + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def setUpClass(cls): + cls.client = Client("127.0.0.1", 10000, timeout=2) + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_put(self): + metrics_for_put = [ + ("test", 0.5, 1), + ("test", 2.0, 2), + ("test", 0.4, 2), + ("load", 301, 3), + ] + for metric, value, timestamp in metrics_for_put: + try: + self.client.put(metric, value, timestamp) + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Ошибка вызова client.put(" + f"'{metric}', {value}, timestamp={timestamp})\n{message}") + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_get_key(self): + try: + rsp = self.client.get("test") + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Ошибка вызова client.get('test')\n{message}") + + metrics_fixture = { + "test": [(1, .5), (2, .4)], + } + self.assertEqual(rsp, metrics_fixture) + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_get_all(self): + try: + rsp = self.client.get("*") + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Ошибка вызова client.get('*')\n{message}") + + metrics_fixture = { + "test": [(1, .5), (2, .4)], + "load": [(3, 301.0)] + } + self.assertEqual(rsp, metrics_fixture) + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_get_not_exists(self): + try: + rsp = self.client.get("key_not_exists") + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Ошибка вызова client.get('key_not_exists')\n{message}") + + self.assertEqual({}, rsp, "check rsp eq {}") + + @patch("socket.create_connection", ServerSocket.create_connection) + @patch("socket.socket", ServerSocket.create_connection) + def test_client_get_client_error(self): + try: + self.assertRaises(ClientError, + self.client.get, "get_client_error") + except ServerSocketException as exp: + message = exp.args[0] + self.fail(f"Некорректно обработано сообщение сервера об ошибке: {message}") diff --git a/6. Финальный проект/1. Финальный проект/Ваши впечатления о курсе.ipynb b/6. Финальный проект/1. Финальный проект/Ваши впечатления о курсе.ipynb new file mode 100644 index 0000000..a95846a --- /dev/null +++ b/6. Финальный проект/1. Финальный проект/Ваши впечатления о курсе.ipynb @@ -0,0 +1,47 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8178efeb", + "metadata": {}, + "source": [ + "# Ваши впечатления о курсе #" + ] + }, + { + "cell_type": "markdown", + "id": "cd79e073", + "metadata": {}, + "source": [ + "Поздравляем Вас с прохождением курса \"Основы программирования на Python\"!\n", + "\n", + "Расскажите, как вам курс? Что было полезного и интересного? Мы будем вам очень признательны, если вы заполните небольшой опрос, который займет 5-7 минут. Ваши ответы помогут нам усовершенствовать курс!\n", + "\n", + "Также нам всегда очень приятно получать вашу обратную связь в виде отзывов и историй.\n", + "\n", + "Спасибо вам, что были с нами!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/6. Финальный проект/1. Финальный проект/Сервер для приема метрик.ipynb b/6. Финальный проект/1. Финальный проект/Сервер для приема метрик.ipynb new file mode 100644 index 0000000..8e53565 --- /dev/null +++ b/6. Финальный проект/1. Финальный проект/Сервер для приема метрик.ipynb @@ -0,0 +1,234 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cb9b2246", + "metadata": {}, + "source": [ + "# Сервер для приема метрик #" + ] + }, + { + "cell_type": "markdown", + "id": "db5acdef", + "metadata": {}, + "source": [ + "В предыдущем блоке вы разработали клиентское сетевое приложение — клиента для сервера метрик, который умеет отправлять и получать всевозможные метрики. Пришло время финального задания — нужно реализовать серверную часть самостоятельно.\n", + "\n", + "Как обычно вам необходимо разработать программу в одном файле-модуле. Сервер должен соответствовать протоколу, который был описан в задании к предыдущему блоку. Он должен уметь принимать от клиентов команды `put` и `get`, разбирать их, и формировать ответ согласно протоколу. По запросу `put` требуется сохранять метрики в структурах данных в памяти процесса. По запросу `get` сервер обязан отдавать данные в правильной последовательности.\n", + "\n", + "На верхнем уровне вашего модуля должна быть объявлена функция `run_server(host, port)` — она принимает адрес и порт, на которых должен быть запущен сервер.\n", + "\n", + "Для проверки правильности решения мы воспользуемся своей реализацией клиента и будем отправлять на ваш сервер `put` и `get` запросы, ожидая в ответ правильные данные от сервера (согласно объявленному протоколу). Все запросы будут выполняться с таймаутом — сервер должен отвечать за приемлемое время.\n", + "\n", + "Сервер должен быть готов к неправильным командам со стороны клиента и отдавать клиенту ошибку в формате, оговоренном в протоколе. В таком случае работа сервера не должна завершаться аварийно.\n", + "\n", + "В последнем блоке мы с вами разбирали пример tcp-сервера на `asyncio`:\n", + "\n", + "```python\n", + "import asyncio\n", + "\n", + "\n", + "class ClientServerProtocol(asyncio.Protocol):\n", + " def connection_made(self, transport):\n", + " self.transport = transport\n", + "\n", + " def data_received(self, data):\n", + " resp = process_data(data.decode())\n", + " self.transport.write(resp.encode())\n", + "\n", + "\n", + "loop = asyncio.get_event_loop()\n", + "coro = loop.create_server(\n", + " ClientServerProtocol,\n", + " \"127.0.0.1\", 8181\n", + ")\n", + "\n", + "server = loop.run_until_complete(coro)\n", + "\n", + "try:\n", + " loop.run_forever()\n", + " \n", + "except KeyboardInterrupt:\n", + " pass\n", + "\n", + "server.close()\n", + "\n", + "loop.run_until_complete(server.wait_closed())\n", + "loop.close()\n", + "```\n", + "\n", + "Данный код создает tcp-соединение для адрса `127.0.0.1:8181` и слушает все входящие запросы. При подключении клиента будет создан новый экземпляр класса `ClientServerProtocol`, а при поступлении новых данных вызовется метод этого объекта - `data_received`. Внутри `asyncio.Protocol` спрятана вся магия обработки запросов через корутины, остается реализовать протокол взаимодействия между клиентом и сервером.\n", + "\n", + "Этот код может использоваться как основа для реализации сервера. Это не обязательное требование. Для реализации задачи вы можете использовать любые вызовы из стандартной библиотеки Python 3. Сервер должен обрабатывать запросы от нескольких клиентов одновременно.\n", + "\n", + "В процессе разработки сервера для тестирования работоспособности вы можете использовать клиент, написанный в предыдущем блоке.\n", + "\n", + "Давайте еще раз посмотрим на текстовый протокол в действии при использовании утилиты `telnet`:\n", + "\n", + "```\n", + "$: telnet 127.0.0.1 8888\n", + "Trying 127.0.0.1...\n", + "Connected to localhost.\n", + "Escape character is '^]'.\n", + "> get test_key\n", + "< ok\n", + "< \n", + "> got test_key\n", + "< error\n", + "< wrong command\n", + "< \n", + "> put test_key 12.0 1503319740\n", + "< ok\n", + "< \n", + "> put test_key 13.0 1503319739\n", + "< ok\n", + "< \n", + "> get test_key \n", + "< ok\n", + "< test_key 13.0 1503319739\n", + "< test_key 12.0 1503319740\n", + "< \n", + "> put another_key 10 1503319739\n", + "< ok\n", + "< \n", + "> get *\n", + "< ok\n", + "< test_key 13.0 1503319739\n", + "< test_key 12.0 1503319740\n", + "< another_key 10.0 1503319739\n", + "< \n", + "```\n", + "\n", + "Также вы можете воспользоваться вспомогательным скриптом, который использует реализацию клиента в пятом блоке, для локального тестирования написанного вами сервера:\n", + "\n", + "```python\n", + "\"\"\"\n", + "Это вспомогательный скрипт для тестирования сервера из задания в 6 блоке.\n", + "\n", + "Для запуска скрипта на локальном компьютере разместите рядом файл client.py,\n", + "где содержится код клиента, который реализован в задание блока 5.\n", + "\n", + "Сначала запускаете ваш сервер на адресе 127.0.0.1 и порту 8888, а затем \n", + "запускаете этот скрипт.\n", + "\n", + "\"\"\"\n", + "\n", + "import sys\n", + "\n", + "from client import Client, ClientProtocolError, ClientSocketError\n", + "\n", + "\n", + "def run(host, port):\n", + "\n", + " client1 = Client(host, port, timeout=5)\n", + " client2 = Client(host, port, timeout=5)\n", + "\n", + " try:\n", + " client1.send(\"malformed command test\\n\")\n", + " client2.send(\"malformed command test\\n\")\n", + "\n", + " except ClientSocketError as err:\n", + " print(f\"Ошибка общения с сервером: {err.__class__}: {err}\")\n", + " sys.exit(1)\n", + "\n", + " except ClientProtocolError:\n", + " pass\n", + "\n", + " else:\n", + " print(\n", + " \"Неверная команда, отправленная серверу, должна возвращать ошибку протокола\"\n", + " )\n", + " sys.exit(1)\n", + "\n", + " try:\n", + " client1.put(\"k1\", 0.25, timestamp=1)\n", + " client2.put(\"k1\", 2.156, timestamp=2)\n", + " client1.put(\"k1\", 0.35, timestamp=3)\n", + " client2.put(\"k2\", 30, timestamp=4)\n", + " client1.put(\"k2\", 40, timestamp=5)\n", + " client1.put(\"k2\", 40, timestamp=5)\n", + "\n", + " except Exception as err:\n", + " print(f\"Ошибка вызова client.put(...) {err.__class__}: {err}\")\n", + " sys.exit(1)\n", + "\n", + " expected_metrics = {\n", + " \"k1\": [(1, 0.25), (2, 2.156), (3, 0.35)],\n", + " \"k2\": [(4, 30.0), (5, 40.0)],\n", + " }\n", + "\n", + " try:\n", + " metrics = client1.get(\"*\")\n", + " if metrics != expected_metrics:\n", + " print(\n", + " f\"client.get('*') вернул неверный результат. Ожидается: {expected_metrics}. Получено: {metrics}\"\n", + " )\n", + " sys.exit(1)\n", + "\n", + " except Exception as err:\n", + " print(f\"Ошибка вызова client.get('*') {err.__class__}: {err}\")\n", + " sys.exit(1)\n", + "\n", + " expected_metrics = {\"k2\": [(4, 30.0), (5, 40.0)]}\n", + "\n", + " try:\n", + " metrics = client2.get(\"k2\")\n", + " if metrics != expected_metrics:\n", + " print(\n", + " f\"client.get('k2') вернул неверный результат. Ожидается: {expected_metrics}. Получено: {metrics}\"\n", + " )\n", + " sys.exit(1)\n", + "\n", + " except Exception as err:\n", + " print(f\"Ошибка вызова client.get('k2') {err.__class__}: {err}\")\n", + " sys.exit(1)\n", + "\n", + " try:\n", + " result = client1.get(\"k3\")\n", + " if result != {}:\n", + " print(\n", + " f\"Ошибка вызова метода get с ключом, который еще не был добавлен. Ожидается: пустой словарь. Получено: {result}\"\n", + " )\n", + " sys.exit(1)\n", + "\n", + " except Exception as err:\n", + " print(\n", + " f\"Ошибка вызова метода get с ключом, который еще не был добавлен: {err.__class__} {err}\"\n", + " )\n", + " sys.exit(1)\n", + "\n", + " print(\"Похоже, что все верно!\")\n", + "\n", + "\n", + "if __name__ == \"__main__\":\n", + " run(\"127.0.0.1\", 8888)\n", + "\n", + "```\n", + "\n", + "Успехов в разработке!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/6. Финальный проект/Readme.ipynb b/6. Финальный проект/Readme.ipynb new file mode 100644 index 0000000..372d4de --- /dev/null +++ b/6. Финальный проект/Readme.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "09a9d448", + "metadata": {}, + "source": [ + "# Финальный проект #" + ] + }, + { + "cell_type": "markdown", + "id": "aef4130c", + "metadata": {}, + "source": [ + "На последнем блоке курса вам предстоит реализовать полноценное серверное приложение для получения метрик от множества клиентов." + ] + }, + { + "cell_type": "markdown", + "id": "e4c42e55", + "metadata": {}, + "source": [ + "## Задачи обучения ##\n", + "\n", + "- Создать своё серверное сетевое приложение." + ] + }, + { + "cell_type": "markdown", + "id": "31c4d4fe", + "metadata": {}, + "source": [ + "## Оглавление ##" + ] + }, + { + "cell_type": "markdown", + "id": "878ed185", + "metadata": {}, + "source": [ + "### Финальный проект ###\n", + "\n", + "- [Сервер для приема метрик](1.%20Финальный%20проект/Сервер%20для%20приема%20метрик.ipynb)\n", + "- [Ваши впечатления о курсе](1.%20Финальный%20проект/Ваши%20впечатления%20о%20курсе.ipynb)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/6. Финальный проект/untitled.txt b/6. Финальный проект/untitled.txt new file mode 100644 index 0000000..e69de29 diff --git a/Readme.ipynb b/Readme.ipynb new file mode 100644 index 0000000..ab686d8 --- /dev/null +++ b/Readme.ipynb @@ -0,0 +1,163 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Defaulting to user installation because normal site-packages is not writeable\n", + "Requirement already satisfied: rise in /home/aap/.local/lib/python3.10/site-packages (5.7.1)\n", + "Requirement already satisfied: requests in /usr/lib/python3/dist-packages (2.25.1)\n", + "Requirement already satisfied: notebook>=6.0 in /usr/lib/python3/dist-packages (from rise) (6.4.8)\n" + ] + } + ], + "source": [ + "! pip install rise requests" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Основы программирования на Python #\n", + "\n", + "##### ФГУП РФЯЦ - ВНИИТФ им. Академ. Е.И. Забабахина #####\n", + "\n", + "[comment]: # (На основе курса от Московского физико-технического института, Mail.Ru Group и фонда развития онлайн-образования «Погружение в Python» https://www.coursera.org/learn/diving-in-python \"Coursera\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Об этом курсе ##\n", + "\n", + "**Python** – простой, гибкий и невероятно популярный язык, который используется практически во всех областях современной разработки.\n", + "\n", + "С его помощью можно создавать веб-приложения, писать игры, заниматься анализом данных, автоматизировать задачи системного администрирования и многое другое." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Курс **«Основы программирования на Python»** читает разработчик, применяющий Python в проектах, которыми ежедневно пользуются наши сотрудники.\n", + "\n", + "Курс покрывает все необходимые для ежедневной работы программиста темы, а также рассказывает про многие особенности языка, которые часто опускают при его изучении." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "В ходе курса вы изучите конструкции языка, типы и структуры данных, функции, научитесь применять объектно-ориентированное и функциональное программирование, узнаете про особенности реализации Python, научитесь писать асинхронный и многопоточный код.\n", + "\n", + "Помимо теории вас ждут практические задания, которые помогут проверить полученные знания и отточить навыки программирования на Python.\n", + "\n", + "После успешного окончания курса вы сможете использовать полученный опыт для разработки проектов различной сложности." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Преподаватель ###\n", + "\n", + "**Александр Михайлов** НИО-3, инженер-программист." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Программа курса ###\n", + "\n", + "1. [Введение в Python](1.%20Введение%20в%20Python/Readme.ipynb) (*8 часов*)\n", + "2. [Структуры данных и функции](2.%20Структуры%20данных%20и%20функции/Readme.ipynb) (*6 часов*)\n", + "3. [Объектно-ориентированное программирование](3.%20Объектно-ориентированное%20программирование/Readme.ipynb) (*11 часов*)\n", + "4. [Углубленный Python](4.%20Углубленный%20Python/Readme.ipynb) (*6 часов*)\n", + "5. [Многопоточное и асинхронное программирование](5.%20Многопоточное%20и%20асинхронное%20программирование/Readme.ipynb) (*8 часов*)\n", + "6. [Финальный проект](6.%20Финальный%20проект/Readme.ipynb) (*5 часов*)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Где задать вопрос? ##\n", + "\n", + "Ваши вопросы можно задать мне по электронной почте ." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Приглашайте на курс ваших коллег, которые хотят научиться программировать на Python. Работа в команде облегчит восприятие материала и подготовит вас к взаимодействию на больших проектах. " + ] + } + ], + "metadata": { + "celltoolbar": "Слайд-шоу", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}