From 993ded3fc1cc24dc36e0d4d4aba09fc45d3b4cac Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 13 Oct 2025 21:01:45 +0300 Subject: [PATCH 01/37] SENATOROVAI/intro-cs/issues/6 (https://github.com/SENATOROVAI/intro-cs/issues/6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes ссылка на https://github.com/SENATOROVAI/intro-cs/issues/6 --- quiz.ipynb | 311 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 quiz.ipynb diff --git a/quiz.ipynb b/quiz.ipynb new file mode 100644 index 00000000..09874abf --- /dev/null +++ b/quiz.ipynb @@ -0,0 +1,311 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "ddbd5fd4", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"SENATOROVAI/intro-cs/issues/6.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "232e46a3", + "metadata": {}, + "source": [ + "1) Как понять, что домашка пришла?\n", + " - В чате homework будет сообщение прими пул.\n", + "2) Как принять домашку?\n", + " - Заходим в Github desktop, затем fetch origin\n", + " - Нажимаем history, смотрим ходим в самый свежий коммит\n", + "3) Зачем нужна кнопка history и какие функции появляются при нажатии правой кнопки мыши на коммит?\n", + " - History - просмотр истории коммитов\n", + " - Amend commit - изменение последнего коммита\n", + " - Reset to commit - отменяет все коммиты, которые идут после укзанного коммита\n", + " - Checkout commit – перемещение по коммитам\n", + " - Reorder commit – изменить порядок коммитов\n", + " - Revert changes in commit – создает новый коммит, который отменяет изменения, внесенные в выбранном коммите, не удаляя сам коммит из истории\n", + " - Create tag... – создает тег для пометки определенного коммита\n", + " - Create branch from commit – позволяет создать новую ветку, начиная с выбранного коммита\n", + " - Cherry-pick commit – копирует коммит из одной ветки в другую\n", + " - Copy SHA – копирует уникальный идентификатор коммита\n", + " - View on GitHub – открывает выбранный коммит на сайте GitHub\n", + " - Copy tag - копирует имя выбранного тега\n", + "3.1) Где брать ссылку на коммит? куда её отправлять? \n", + " - GitHub вкладка коммит\n", + " - Review запросы?\n", + "4) Что такое файл лога?\n", + " - Файл для записи прогресса в учебе\n", + "4.1) Когда нужно его пушить?\n", + " - Когда сделали домашку\n", + "5) Что такое интерпритатор?\n", + " - Программа, которая читает и выполняет код\n", + "6) Где можно выбрать интерпритатор?\n", + " - base\n", + " - Select kernel\n", + " - или Ctrl + Shift + P, затем Python: Select Interpreter\n", + "7) Что такое модуль?\n", + " - Файл с расширением .py, который содержит код и может быть повторно использован в других программах\n", + "8) Как создать и отправить коммит?\n", + " - GitHub Desktop - название коммита - commit to main - push origin\n", + "9) Как посмотреть что коммит точно отправлен и находится в github?\n", + " - History - commit - view on github\n", + "10) Какая команда показывает что код не прошёл проверки на ошибки?\n", + " - pre-commit run --all-files\n", + "10.1) Напишите список линтеров которые используются для проверки кода и дайте их краткую характеристику.\n", + " - black — автоформаттер Python кода согласно единому стилю PEP8\n", + " - flake8 — для проверки стиля и потенциальных ошибок\n", + " - mypy — анализатор для проверки соответствия типов\n", + " - nbqa-pydocstyle — проверяет соответствие docstring документации в notebook-коде внутренним стандартам\n", + " - codespell — ищет орфографические ошибки и опечатки в коде\n", + " - pylint — проверяет стиль, ошибки и сложность кода\n", + "11) Как узнать какой именно линтер не прошёл проверку?\n", + " - Failed напротив линтера, который не прошел проверку\n", + "12) Линтер Pylint видит markdown?\n", + " - нет\n", + "13) Номер ячейки в терминале и номер ячейки в vs code может отличаться? в каком случае?\n", + " - да, линтер не видит markdown и номер ячейки может при проверке линтера может отличаться\n", + "14) Где посмотреть номер ячейки в vscode?\n", + " - внизу справа\n", + "15) В каком формате ipynb отправляется в гитхаб? причём здесь JSON?\n", + " - GitHub принимает файлы .ipynb в формате JSON. Jupyter Notebook с расширением .ipynb представляет файл в формате JSON. \n", + "16) Где посмотреть в какой ячейке ошибка?\n", + " - В терминале после запуска pre-commit run --all-files сообщение об ошибке и номере ячейки\n", + "17) Как запустить терминал?\n", + " - View - Terminal \n", + " - Ctrl+`\n", + "18) Что такое линтер?\n", + " - Программа, которая проверяет код на наличие ошибок и несоответствий стандартам написания кода\n", + "19) В какой сайт нужно вставлять код ошибки если ошибка связана с pylint?\n", + " - https://pylint.readthedocs.io/en/stable/\n", + "20) Секция pydocstyle в большинстве случае автоматический закрывается после исправления ошибок в каком линтере?\n", + " - pylint\n", + "21) Что такое описание модуля? Оно должно отражать информацию о том что находится в модуле?\n", + " - Описание файла. Да\n", + "21.1) С какой git команды начинается утро программиста?\n", + " - git pull, git status\n", + "22) После внесения изменений в файлах, кнопка open in vs code пропадает в кошке, как по другому открыть vs code из кошки?\n", + " - current repository - open in VSCode\n", + "23) Что такое stash? \n", + " Общее объяснение концепции.\n", + " - Команда в Git, которая позволяет временно сохранить все сделанные, но ещё не зафиксированные изменения в коде\n", + "23.1) Как сохранить стэш?\n", + " git командa(подсказка: https://t.me/c/1937296927/3602/19531): \n", + " - git stash\n", + " Кнопка в vs code: Ctrl + Shift + G\n", + "23.2) Как восстановить стэш(подсказка: https://t.me/c/1937296927/3602/25747)?:\n", + " - git stash apply\n", + " git команда(подсказка: https://t.me/c/1937296927/3602/19531)?:\n", + "23.3) Различие между стэшем и коммитом. \n", + " Когда лучше сохранить изменения в стэше, а когда коммитить.\n", + " - Git commit — для постоянного сохранения изменений\n", + " - Git stash — для временного сохранения изменений\n", + " - Git commit: изменения полностью готовы; для общего репозитория и совместной работы\n", + " - Git stash: когда надо переключить на другую ветку, а текущий код не готов; неполные изменения; если нужно отложить работу\n", + "23.4) Как просмотреть список сохраненных стэшей?\n", + " - git stash list \n", + " git команда (подсказка: https://t.me/c/1937296927/3602/19531):\n", + "23.5) Как удалить стэш? \n", + " Команды для удаления отдельных стэшей или всех сразу.\n", + " git команда (подсказка: https://t.me/c/1937296927/3602/19531):\n", + " - git stash drop\n", + "23.6) Практические примеры использования стэша. \n", + " Краткие сценарии, где стэш помогает.\n", + " - Срочное переключение задачи\n", + " - Экспериментальные изменения\n", + " - Создание новой ветки из stash\n", + " - Переключение веток\n", + "24) Где посмотреть что есть конфликт в файлах?\n", + " - Github Desktop восклицательный знак напротив файла\n", + "24.1) Когда он появляется?\n", + " - Когда несколько пользователей хотят сохранить изменения в одном файле и гит не понимает какие изменения сохранить\n", + "25) Как решить конфликт в файлах?\n", + " 1. Stash changes (сохраняем локальные изменения) \n", + " 2. Fetch origin (принимаем изменения из интернета)\n", + " 3. Stashed changes - restore (восстанавливаем локальные изменения)\n", + " 4. Open in VSCode\n", + " 5. Решаем конфликт\n", + " 6. Resolve in merge editor \n", + " 7. Complete merge\n", + "26) Напишиие правильное утверждение\n", + "-Зелёное то что пришло с гитхаба и синее локальные изменения или синее то что пришло с гитхаба и зелёное это локальные изменения \n", + " - Зеленое гитхаб, синие локальные изменения\n", + "27) Если мы работаем в одном файле, можно ли принять pull после того как вы спрячете в стэш свои изменения? \n", + " - Да\n", + "27.1) Что может произойти когда stash восстановите после принятия pull?\n", + " - git уведомит о конфликте\n", + "28) Сколько способов решения конфликтов было показано в видео? Напишите ЧИСЛО и укажите их способы.\n", + " 1. Принять изменения с гитхаба\n", + " 2. Принять локальные изменения\n", + " 3. Оба изменения\n", + " 4. Ручные правки \n", + "29) Что делает кнопка complete merge?\n", + " - Завершить слияние (конфликт)\n", + "30) В какой чат нужно писать если остались вопросы?\n", + " - help me\n", + "31) Что такое FORK? Зачем его делают? \n", + " - Копирование репозитория\n", + "32) Как скачать форкнутый репозиторий на локальный компьютер?\n", + " - Github desktop - clone repository - clone\n", + "33) С какой вероятностью ваши ошибки были уже решены? и кто их решил?\n", + " - 99%. Преподаватель или студенты\n", + "34) Как создать файл в vs code?\n", + " - File - New file\n", + "35) Файл лога нужно заполнять в конце каждого урока?\n", + " - Да\n", + "==================\n", + "\n", + "Дополнительные вопросы:\n", + "1)Какая команда конвертирует файл в py из ipynb? \n", + "подсказка https://t.me/c/1937296927/1/26527 \n", + " - jupyter nbconvert --to script notebook.ipynb\n", + "2) Что такое пакетный менеджер? Вы пользуетесь пакетным менеджером conda или pip? Какой лучше использовать для дата сайнс?\n", + " - Программа, которая автоматизируют установку, обновление, настройку и удаление программного обеспечения и библиотек. Conda\n", + "3) Почему расширение py лучше чем ipynb?\n", + " - .py файлы удобно использовать при контроле версий с помощью Git, так как они являются обычными текстовыми файлами\n", + "4) Что такое pep8? \n", + "подсказка:https://peps.python.org/pep-0008/\n", + " - PEP 8 — это официальный стиль написания кода для языка Python\n", + "4.1) линтеры проверяют на соблюдение pep8?\n", + " - Да\n", + "4.2) Какая нотация используется для создания переменных? \n", + "ответ на 85-95 страницы https://t.me/c/1937296927/1/16676\n", + " - snake_case\n", + "4.3) Может ли переменная состоять из одной буквы например андерскор \"_\" ?\n", + " - да\n", + "4.4) Зачем и где мы используем андерскор _ \n", + " - Игнорирование значений\n", + " - Для обозначения внутренних/приватных переменных\n", + " - Как часть \"магических\" методов Python (init)\n", + "4.5) По PEP8 допустима переменная в одну букву?\n", + "ответ на 85-95 страницы https://t.me/c/1937296927/1/16676\n", + " - да" + ] + }, + { + "cell_type": "markdown", + "id": "d152b6f8", + "metadata": {}, + "source": [ + "1. Как включить автосохранение данных в VSCODE?\n", + " - File - autosave\n", + "2. Как настроить перенос строки? \n", + " - Preferences - settings - wordwrapcolumn\n", + "3. Сколько символов по pep8 разрешено на строке?\n", + " - 79\n", + "4. Какие способы переноса строк показаны в видео:\n", + " - 4.4, 4.6\n", + "4.1 Строки с использованием обратного слэша (\\)\n", + "\n", + "string_continued = \"This is a long string that we want to \" \\\n", + " \"split across multiple lines.\"\n", + "print(string_continued)\n", + "\n", + "4.2 Тройные кавычки (''' или \"\"\") \n", + "\n", + "multi_line_string = \"\"\"This is a string that spans\n", + "multiple lines. You can write freely\n", + "and it will keep the line breaks.\"\"\"\n", + "print(multi_line_string)\n", + "\n", + "4.3 Создание списка строк и объединение с помощью join\n", + "\n", + "strings = [\n", + " \"This is the first line.\",\n", + " \"This is the second line.\",\n", + " \"This is the third line.\"\n", + "]\n", + "result = \"\\n\".join(strings) # Используем перенос строк '\\n'\n", + "print(result)\n", + "\n", + "4.4 Использование круглых скобок для продолжения строки\n", + "long_string = (\n", + " \"This is a very long string that I would like to \"\n", + " \"continue on the next line.\"\n", + ")\n", + "print(long_string)\n", + "\n", + "4.5 Форматированные строки (f-строки) с использованием скобок\n", + "letter_a = 5\n", + "letter_b = 6\n", + "product_ab = letter_a * letter_b\n", + "\n", + "message = (\n", + " f\"when {letter_a} is multiplied by {letter_b}, \"\n", + " f\"the result is {product_ab}\"\n", + ")\n", + "print(message)\n", + "\n", + "4.6 Сложение строк с помощью +\n", + "\n", + "string_part1 = \"This is the first part, \"\n", + "string_part2 = \"and this is the second part.\"\n", + "full_string = string_part1 + string_part2\n", + "print(full_string)\n", + "\n", + "5. Проверка на ошибки c помощью кнопки problems, где она находится?\n", + " - Нижняя панель - Problems\n", + "6. Где в vscode находится клиент гита? как в нём отправить коммит? как принять домашку?\n", + " - Слева три кружочка\n", + " - Source control - Commit and push\n", + " - Домашка принимается кнопкой Pull\n", + "7. Что такое GIT? он локальный? В нём можно посмотреть историю изменений файлов и вернуться к любому коммиту?\n", + " - Git - система контроля версий\n", + " - Локальный\n", + " - Да\n", + "8. Как вставить картинку в маркдаун?\n", + " - Ctrl+V\n", + "9. Где посмотреть длину строки в vs code?\n", + " - Внизу окна VS Code в строке состояния\n", + "10. Как поменять тип ячейки с питона на маркдаун?\n", + " - В ячейке справа снизу\n", + "11. Как запустить сразу все ячейки в юпитере?\n", + " - Run all наверху\n", + "12. Как изменить размер картинки в юпитере? Нужно для этого знать HTML?\n", + " - \n", + " - Да\n", + "13. Какой хоткей чтобы запустить ячейку с смещением на следующую?\n", + " - Shift+enter\n", + "14. Как включить отображение номеров строк в юпитере(Cell line numbers)?\n", + " - Show cell line numbers или клавиша L\n", + "15. Что такое \"Go To\" чем это полезно? Как перейти сразу на ошибочную ячейку?\n", + " - Перейти на ячейку с ошибкой\n", + " - Нажать на \"Go To\"\n", + "16. Как очистить вывод ячеек которые уже запущены?\n", + " - Clear all outputs\n", + "17. Как работать одновременно в нескольких файлах в VSCODE? Что такое SPLIT?\n", + " - Split edit\n", + " - Разделение редактора на несколько панелей для работы с разными файлами одновременно\n", + "18. Каким сочетанием убирается левый сайдбар?\n", + " - Ctrl+B\n", + "19. Кнопка два листочка это наши локальные файлы?\n", + " - Да\n", + "20. Какая ошибка появилась в трассировке при запуске всех ячеек DICT или LIST?\n", + " - Name 'Dict' in not defined\n", + "21. Вы ознакомились с https://t.me/c/1937296927/832/19307? и https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet?\n", + " - Да\n", + "22. Что такое валидация?\n", + " - Проверка кода на правильность\n", + "23. Что такое трассировка ошибки?\n", + " - Текстовое представление ошибки\n", + "24. Что значит отвалился интерпритатор?\n", + " - Интерпритатор завершил работу из-за ошибки" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 3ddc6b4205800cc94501b0848a30ecd4c03a59ab Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 13 Oct 2025 21:37:11 +0300 Subject: [PATCH 02/37] Create quiz.py --- quiz.py | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 quiz.py diff --git a/quiz.py b/quiz.py new file mode 100644 index 00000000..f393b5fb --- /dev/null +++ b/quiz.py @@ -0,0 +1,273 @@ +"""SENATOROVAI/intro-cs/issues/6.""" + +# 1) Как понять, что домашка пришла? +# - В чате homework будет сообщение прими пул. +# 2) Как принять домашку? +# - Заходим в Github desktop, затем fetch origin +# - Нажимаем history, смотрим ходим в самый свежий коммит +# 3) Зачем нужна кнопка history и какие функции появляются при нажатии правой кнопки мыши на коммит? +# - History - просмотр истории коммитов +# - Amend commit - изменение последнего коммита +# - Reset to commit - отменяет все коммиты, которые идут после укзанного коммита +# - Checkout commit – перемещение по коммитам +# - Reorder commit – изменить порядок коммитов +# - Revert changes in commit – создает новый коммит, который отменяет изменения, внесенные в выбранном коммите, не удаляя сам коммит из истории +# - Create tag... – создает тег для пометки определенного коммита +# - Create branch from commit – позволяет создать новую ветку, начиная с выбранного коммита +# - Cherry-pick commit – копирует коммит из одной ветки в другую +# - Copy SHA – копирует уникальный идентификатор коммита +# - View on GitHub – открывает выбранный коммит на сайте GitHub +# - Copy tag - копирует имя выбранного тега +# 3.1) Где брать ссылку на коммит? куда её отправлять? +# - GitHub вкладка коммит +# - Review запросы? +# 4) Что такое файл лога? +# - Файл для записи прогресса в учебе +# 4.1) Когда нужно его пушить? +# - Когда сделали домашку +# 5) Что такое интерпритатор? +# - Программа, которая читает и выполняет код +# 6) Где можно выбрать интерпритатор? +# - base +# - Select kernel +# - или Ctrl + Shift + P, затем Python: Select Interpreter +# 7) Что такое модуль? +# - Файл с расширением .py, который содержит код и может быть повторно использован в других программах +# 8) Как создать и отправить коммит? +# - GitHub Desktop - название коммита - commit to main - push origin +# 9) Как посмотреть что коммит точно отправлен и находится в github? +# - History - commit - view on github +# 10) Какая команда показывает что код не прошёл проверки на ошибки? +# - pre-commit run --all-files +# 10.1) Напишите список линтеров которые используются для проверки кода и дайте их краткую характеристику. +# - black — автоформаттер Python кода согласно единому стилю PEP8 +# - flake8 — для проверки стиля и потенциальных ошибок +# - mypy — анализатор для проверки соответствия типов +# - nbqa-pydocstyle — проверяет соответствие docstring документации в notebook-коде внутренним стандартам +# - codespell — ищет орфографические ошибки и опечатки в коде +# - pylint — проверяет стиль, ошибки и сложность кода +# 11) Как узнать какой именно линтер не прошёл проверку? +# - Failed напротив линтера, который не прошел проверку +# 12) Линтер Pylint видит markdown? +# - нет +# 13) Номер ячейки в терминале и номер ячейки в vs code может отличаться? в каком случае? +# - да, линтер не видит markdown и номер ячейки может при проверке линтера может отличаться +# 14) Где посмотреть номер ячейки в vscode? +# - внизу справа +# 15) В каком формате ipynb отправляется в гитхаб? причём здесь JSON? +# - GitHub принимает файлы .ipynb в формате JSON. Jupyter Notebook с расширением .ipynb представляет файл в формате JSON. +# 16) Где посмотреть в какой ячейке ошибка? +# - В терминале после запуска pre-commit run --all-files сообщение об ошибке и номере ячейки +# 17) Как запустить терминал? +# - View - Terminal +# - Ctrl+` +# 18) Что такое линтер? +# - Программа, которая проверяет код на наличие ошибок и несоответствий стандартам написания кода +# 19) В какой сайт нужно вставлять код ошибки если ошибка связана с pylint? +# - https://pylint.readthedocs.io/en/stable/ +# 20) Секция pydocstyle в большинстве случае автоматический закрывается после исправления ошибок в каком линтере? +# - pylint +# 21) Что такое описание модуля? Оно должно отражать информацию о том что находится в модуле? +# - Описание файла. Да +# 21.1) С какой git команды начинается утро программиста? +# - git pull, git status +# 22) После внесения изменений в файлах, кнопка open in vs code пропадает в кошке, как по другому открыть vs code из кошки? +# - current repository - open in VSCode +# 23) Что такое stash? +# Общее объяснение концепции. +# - Команда в Git, которая позволяет временно сохранить все сделанные, но ещё не зафиксированные изменения в коде +# 23.1) Как сохранить стэш? +# git командa(подсказка: https://t.me/c/1937296927/3602/19531): +# - git stash +# Кнопка в vs code: Ctrl + Shift + G +# 23.2) Как восстановить стэш(подсказка: https://t.me/c/1937296927/3602/25747)?: +# - git stash apply +# git команда(подсказка: https://t.me/c/1937296927/3602/19531)?: +# 23.3) Различие между стэшем и коммитом. +# Когда лучше сохранить изменения в стэше, а когда коммитить. +# - Git commit — для постоянного сохранения изменений +# - Git stash — для временного сохранения изменений +# - Git commit: изменения полностью готовы; для общего репозитория и совместной работы +# - Git stash: когда надо переключить на другую ветку, а текущий код не готов; неполные изменения; если нужно отложить работу +# 23.4) Как просмотреть список сохраненных стэшей? +# - git stash list +# git команда (подсказка: https://t.me/c/1937296927/3602/19531): +# 23.5) Как удалить стэш? +# Команды для удаления отдельных стэшей или всех сразу. +# git команда (подсказка: https://t.me/c/1937296927/3602/19531): +# - git stash drop +# 23.6) Практические примеры использования стэша. +# Краткие сценарии, где стэш помогает. +# - Срочное переключение задачи +# - Экспериментальные изменения +# - Создание новой ветки из stash +# - Переключение веток +# 24) Где посмотреть что есть конфликт в файлах? +# - Github Desktop восклицательный знак напротив файла +# 24.1) Когда он появляется? +# - Когда несколько пользователей хотят сохранить изменения в одном файле и гит не понимает какие изменения сохранить +# 25) Как решить конфликт в файлах? +# 1. Stash changes (сохраняем локальные изменения) +# 2. Fetch origin (принимаем изменения из интернета) +# 3. Stashed changes - restore (восстанавливаем локальные изменения) +# 4. Open in VSCode +# 5. Решаем конфликт +# 6. Resolve in merge editor +# 7. Complete merge +# 26) Напишиие правильное утверждение +# -Зелёное то что пришло с гитхаба и синее локальные изменения или синее то что пришло с гитхаба и зелёное это локальные изменения +# - Зеленое гитхаб, синие локальные изменения +# 27) Если мы работаем в одном файле, можно ли принять pull после того как вы спрячете в стэш свои изменения? +# - Да +# 27.1) Что может произойти когда stash восстановите после принятия pull? +# - git уведомит о конфликте +# 28) Сколько способов решения конфликтов было показано в видео? Напишите ЧИСЛО и укажите их способы. +# 1. Принять изменения с гитхаба +# 2. Принять локальные изменения +# 3. Оба изменения +# 4. Ручные правки +# 29) Что делает кнопка complete merge? +# - Завершить слияние (конфликт) +# 30) В какой чат нужно писать если остались вопросы? +# - help me +# 31) Что такое FORK? Зачем его делают? +# - Копирование репозитория +# 32) Как скачать форкнутый репозиторий на локальный компьютер? +# - Github desktop - clone repository - clone +# 33) С какой вероятностью ваши ошибки были уже решены? и кто их решил? +# - 99%. Преподаватель или студенты +# 34) Как создать файл в vs code? +# - File - New file +# 35) Файл лога нужно заполнять в конце каждого урока? +# - Да +# ================== +# +# Дополнительные вопросы: +# 1)Какая команда конвертирует файл в py из ipynb? +# подсказка https://t.me/c/1937296927/1/26527 +# - jupyter nbconvert --to script notebook.ipynb +# 2) Что такое пакетный менеджер? Вы пользуетесь пакетным менеджером conda или pip? Какой лучше использовать для дата сайнс? +# - Программа, которая автоматизируют установку, обновление, настройку и удаление программного обеспечения и библиотек. Conda +# 3) Почему расширение py лучше чем ipynb? +# - .py файлы удобно использовать при контроле версий с помощью Git, так как они являются обычными текстовыми файлами +# 4) Что такое pep8? +# подсказка:https://peps.python.org/pep-0008/ +# - PEP 8 — это официальный стиль написания кода для языка Python +# 4.1) линтеры проверяют на соблюдение pep8? +# - Да +# 4.2) Какая нотация используется для создания переменных? +# ответ на 85-95 страницы https://t.me/c/1937296927/1/16676 +# - snake_case +# 4.3) Может ли переменная состоять из одной буквы например андерскор "_" ? +# - да +# 4.4) Зачем и где мы используем андерскор _ +# - Игнорирование значений +# - Для обозначения внутренних/приватных переменных +# - Как часть "магических" методов Python (init) +# 4.5) По PEP8 допустима переменная в одну букву? +# ответ на 85-95 страницы https://t.me/c/1937296927/1/16676 +# - да + +# 1. Как включить автосохранение данных в VSCODE? +# - File - autosave +# 2. Как настроить перенос строки? +# - Preferences - settings - wordwrapcolumn +# 3. Сколько символов по pep8 разрешено на строке? +# - 79 +# 4. Какие способы переноса строк показаны в видео: +# - 4.4, 4.6 +# 4.1 Строки с использованием обратного слэша (\) +# +# string_continued = "This is a long string that we want to " \ +# "split across multiple lines." +# print(string_continued) +# +# 4.2 Тройные кавычки (''' или """) +# +# multi_line_string = """This is a string that spans +# multiple lines. You can write freely +# and it will keep the line breaks.""" +# print(multi_line_string) +# +# 4.3 Создание списка строк и объединение с помощью join +# +# strings = [ +# "This is the first line.", +# "This is the second line.", +# "This is the third line." +# ] +# result = "\n".join(strings) # Используем перенос строк '\n' +# print(result) +# +# 4.4 Использование круглых скобок для продолжения строки +# long_string = ( +# "This is a very long string that I would like to " +# "continue on the next line." +# ) +# print(long_string) +# +# 4.5 Форматированные строки (f-строки) с использованием скобок +# letter_a = 5 +# letter_b = 6 +# product_ab = letter_a * letter_b +# +# message = ( +# f"when {letter_a} is multiplied by {letter_b}, " +# f"the result is {product_ab}" +# ) +# print(message) +# +# 4.6 Сложение строк с помощью + +# +# string_part1 = "This is the first part, " +# string_part2 = "and this is the second part." +# full_string = string_part1 + string_part2 +# print(full_string) +# +# 5. Проверка на ошибки c помощью кнопки problems, где она находится? +# - Нижняя панель - Problems +# 6. Где в vscode находится клиент гита? как в нём отправить коммит? как принять домашку? +# - Слева три кружочка +# - Source control - Commit and push +# - Домашка принимается кнопкой Pull +# 7. Что такое GIT? он локальный? В нём можно посмотреть историю изменений файлов и вернуться к любому коммиту? +# - Git - система контроля версий +# - Локальный +# - Да +# 8. Как вставить картинку в маркдаун? +# - Ctrl+V +# 9. Где посмотреть длину строки в vs code? +# - Внизу окна VS Code в строке состояния +# 10. Как поменять тип ячейки с питона на маркдаун? +# - В ячейке справа снизу +# 11. Как запустить сразу все ячейки в юпитере? +# - Run all наверху +# 12. Как изменить размер картинки в юпитере? Нужно для этого знать HTML? +# - +# - Да +# 13. Какой хоткей чтобы запустить ячейку с смещением на следующую? +# - Shift+enter +# 14. Как включить отображение номеров строк в юпитере(Cell line numbers)? +# - Show cell line numbers или клавиша L +# 15. Что такое "Go To" чем это полезно? Как перейти сразу на ошибочную ячейку? +# - Перейти на ячейку с ошибкой +# - Нажать на "Go To" +# 16. Как очистить вывод ячеек которые уже запущены? +# - Clear all outputs +# 17. Как работать одновременно в нескольких файлах в VSCODE? Что такое SPLIT? +# - Split edit +# - Разделение редактора на несколько панелей для работы с разными файлами одновременно +# 18. Каким сочетанием убирается левый сайдбар? +# - Ctrl+B +# 19. Кнопка два листочка это наши локальные файлы? +# - Да +# 20. Какая ошибка появилась в трассировке при запуске всех ячеек DICT или LIST? +# - Name 'Dict' in not defined +# 21. Вы ознакомились с https://t.me/c/1937296927/832/19307? и https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet? +# - Да +# 22. Что такое валидация? +# - Проверка кода на правильность +# 23. Что такое трассировка ошибки? +# - Текстовое представление ошибки +# 24. Что значит отвалился интерпритатор? +# - Интерпритатор завершил работу из-за ошибки From 9bf5c33cdb5e4d2ea5c79cbf87c130afb60e70c9 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 20 Oct 2025 13:56:45 +0300 Subject: [PATCH 03/37] SENATOROVAI/intro-cs/issues/4 (https://github.com/SENATOROVAI/intro-cs/issues/4) Closes https://github.com/SENATOROVAI/intro-cs/issues/4 --- python/cpython.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 python/cpython.py diff --git a/python/cpython.py b/python/cpython.py new file mode 100644 index 00000000..33d716b3 --- /dev/null +++ b/python/cpython.py @@ -0,0 +1,60 @@ +"""[TASK] Cpython #4.""" + +# 1. Что такое CPython и чем он отличается от Python? +# - Реализация языка Python, написанная на C. +# - Python - это спецификация языка программирования, а CPython - эго его интерпретатор. +# 3. Сколько существует реализаций Python, и какая из них самая популярная? +# - 6 реализаций: CPython, PyPy, Jython, IronPython, Brython, Nuitka. +# - CPython самая популярная реализация. +# 4. На каком языке написан CPython? +# - С. +# 5. Кто создал CPython? +# - Гвидо ван Россум. +# 6. Почему Python считается быстрым, несмотря на то, что это интерпретируемый язык? +# - Интерпретатор компилирует исходный код в байткод, который затем исполняется виртуальной машиной на С. +# 7. Напишите путь к Интерпретатору CPython на вашем компьютере. +# - C:\Users\nigma.DESKTOP-55Q3CA4\AppData\Local\Programs\Python\Python314\python.exe +# 8. Что содержится в папке include в CPython? +# - файлы на языке C, необходимые для компиляции расширений и взаимодействия с ядром интерпретатора Python. +# 9. Где можно найти исходный код CPython дайте ссылку на репозиторий гитхаб. +# - https://github.com/python/cpython +# 10. Как работает интерпретатор CPython при выполнении кода? +# 1. Читает исходный код, проверяет его синтаксис и форматирование. +# 2. Трансформирует исходный код в байт-код. +# 3. Передает байт-код виртуальной машине. +# 11. Какая команда используется для запуска файла с помощью CPython? +# - python имя_файла.py. +# 12. Можно ли запускать текстовые файлы через интерпретатор Python? Почему? +# - Можно, если в них содержится правильный Python код. +# - Интерпретатор читает последовательность символов из файла и пытается выполнить их как команды на языке Python. +# 13. Как указать путь к интерпретатору и файлу для выполнения кода? +# - ПКМ - свойства - расположение файла. +# - Вставить в командную строку. +# 14. Чем PyPy отличается от CPython? +# - Работает быстрее за счёт JIT-компиляции. +# 15. Почему PyPy не может использоваться для всех проектов на Python? +# - Есть проблемы с совместимостью с библиотеками, использующими C-расширения, такими как NumPy или SciPy. +# 16. Где можно скачать PyPy? +# - https://pypy.org/download.html +# 17. Как установить PyPy после скачивания? +# - Распаковать архив. +# 18. Как запустить файл с помощью PyPy? +# - Путь к интерпретатору PyPy пробел путь к файлу в командной строке. +# 19. Почему PyPy выполняет код быстрее, чем CPython? +# - Преобразует в машинный код наиболее часто используемые компоненты кода, и оптимизирует его. +# +# Практические задания +# +# 2. Исследование структуры CPython +# Найдите папку, где установлен Python (например, через команду which python в терминале или свойства ярлыка). +# Откройте папку include и изучите её содержимое. Какое количество файлов на C там есть? +# - 79. +# 5. Сравнение производительности CPython и PyPy +# - CPython: +# Result: 49999995000000 +# Execution time: 0.4225647449493408 seconds +# - PyPy: +# Result: 49999995000000 +# Execution time: 0.006410121917724609 seconds +# - Вывод: +# PyPy быстрее CPython примерно в 65 раз. From 94383c790c9645f0434cdbbfc21ef481abb745e4 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 20 Oct 2025 14:24:45 +0300 Subject: [PATCH 04/37] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BA=D0=B2=D0=B8=D0=B7=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quiz.ipynb | 311 ----------------------------------------------------- quiz.py | 273 ---------------------------------------------- 2 files changed, 584 deletions(-) delete mode 100644 quiz.ipynb delete mode 100644 quiz.py diff --git a/quiz.ipynb b/quiz.ipynb deleted file mode 100644 index 09874abf..00000000 --- a/quiz.ipynb +++ /dev/null @@ -1,311 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "ddbd5fd4", - "metadata": {}, - "outputs": [], - "source": [ - "\"\"\"SENATOROVAI/intro-cs/issues/6.\"\"\"" - ] - }, - { - "cell_type": "markdown", - "id": "232e46a3", - "metadata": {}, - "source": [ - "1) Как понять, что домашка пришла?\n", - " - В чате homework будет сообщение прими пул.\n", - "2) Как принять домашку?\n", - " - Заходим в Github desktop, затем fetch origin\n", - " - Нажимаем history, смотрим ходим в самый свежий коммит\n", - "3) Зачем нужна кнопка history и какие функции появляются при нажатии правой кнопки мыши на коммит?\n", - " - History - просмотр истории коммитов\n", - " - Amend commit - изменение последнего коммита\n", - " - Reset to commit - отменяет все коммиты, которые идут после укзанного коммита\n", - " - Checkout commit – перемещение по коммитам\n", - " - Reorder commit – изменить порядок коммитов\n", - " - Revert changes in commit – создает новый коммит, который отменяет изменения, внесенные в выбранном коммите, не удаляя сам коммит из истории\n", - " - Create tag... – создает тег для пометки определенного коммита\n", - " - Create branch from commit – позволяет создать новую ветку, начиная с выбранного коммита\n", - " - Cherry-pick commit – копирует коммит из одной ветки в другую\n", - " - Copy SHA – копирует уникальный идентификатор коммита\n", - " - View on GitHub – открывает выбранный коммит на сайте GitHub\n", - " - Copy tag - копирует имя выбранного тега\n", - "3.1) Где брать ссылку на коммит? куда её отправлять? \n", - " - GitHub вкладка коммит\n", - " - Review запросы?\n", - "4) Что такое файл лога?\n", - " - Файл для записи прогресса в учебе\n", - "4.1) Когда нужно его пушить?\n", - " - Когда сделали домашку\n", - "5) Что такое интерпритатор?\n", - " - Программа, которая читает и выполняет код\n", - "6) Где можно выбрать интерпритатор?\n", - " - base\n", - " - Select kernel\n", - " - или Ctrl + Shift + P, затем Python: Select Interpreter\n", - "7) Что такое модуль?\n", - " - Файл с расширением .py, который содержит код и может быть повторно использован в других программах\n", - "8) Как создать и отправить коммит?\n", - " - GitHub Desktop - название коммита - commit to main - push origin\n", - "9) Как посмотреть что коммит точно отправлен и находится в github?\n", - " - History - commit - view on github\n", - "10) Какая команда показывает что код не прошёл проверки на ошибки?\n", - " - pre-commit run --all-files\n", - "10.1) Напишите список линтеров которые используются для проверки кода и дайте их краткую характеристику.\n", - " - black — автоформаттер Python кода согласно единому стилю PEP8\n", - " - flake8 — для проверки стиля и потенциальных ошибок\n", - " - mypy — анализатор для проверки соответствия типов\n", - " - nbqa-pydocstyle — проверяет соответствие docstring документации в notebook-коде внутренним стандартам\n", - " - codespell — ищет орфографические ошибки и опечатки в коде\n", - " - pylint — проверяет стиль, ошибки и сложность кода\n", - "11) Как узнать какой именно линтер не прошёл проверку?\n", - " - Failed напротив линтера, который не прошел проверку\n", - "12) Линтер Pylint видит markdown?\n", - " - нет\n", - "13) Номер ячейки в терминале и номер ячейки в vs code может отличаться? в каком случае?\n", - " - да, линтер не видит markdown и номер ячейки может при проверке линтера может отличаться\n", - "14) Где посмотреть номер ячейки в vscode?\n", - " - внизу справа\n", - "15) В каком формате ipynb отправляется в гитхаб? причём здесь JSON?\n", - " - GitHub принимает файлы .ipynb в формате JSON. Jupyter Notebook с расширением .ipynb представляет файл в формате JSON. \n", - "16) Где посмотреть в какой ячейке ошибка?\n", - " - В терминале после запуска pre-commit run --all-files сообщение об ошибке и номере ячейки\n", - "17) Как запустить терминал?\n", - " - View - Terminal \n", - " - Ctrl+`\n", - "18) Что такое линтер?\n", - " - Программа, которая проверяет код на наличие ошибок и несоответствий стандартам написания кода\n", - "19) В какой сайт нужно вставлять код ошибки если ошибка связана с pylint?\n", - " - https://pylint.readthedocs.io/en/stable/\n", - "20) Секция pydocstyle в большинстве случае автоматический закрывается после исправления ошибок в каком линтере?\n", - " - pylint\n", - "21) Что такое описание модуля? Оно должно отражать информацию о том что находится в модуле?\n", - " - Описание файла. Да\n", - "21.1) С какой git команды начинается утро программиста?\n", - " - git pull, git status\n", - "22) После внесения изменений в файлах, кнопка open in vs code пропадает в кошке, как по другому открыть vs code из кошки?\n", - " - current repository - open in VSCode\n", - "23) Что такое stash? \n", - " Общее объяснение концепции.\n", - " - Команда в Git, которая позволяет временно сохранить все сделанные, но ещё не зафиксированные изменения в коде\n", - "23.1) Как сохранить стэш?\n", - " git командa(подсказка: https://t.me/c/1937296927/3602/19531): \n", - " - git stash\n", - " Кнопка в vs code: Ctrl + Shift + G\n", - "23.2) Как восстановить стэш(подсказка: https://t.me/c/1937296927/3602/25747)?:\n", - " - git stash apply\n", - " git команда(подсказка: https://t.me/c/1937296927/3602/19531)?:\n", - "23.3) Различие между стэшем и коммитом. \n", - " Когда лучше сохранить изменения в стэше, а когда коммитить.\n", - " - Git commit — для постоянного сохранения изменений\n", - " - Git stash — для временного сохранения изменений\n", - " - Git commit: изменения полностью готовы; для общего репозитория и совместной работы\n", - " - Git stash: когда надо переключить на другую ветку, а текущий код не готов; неполные изменения; если нужно отложить работу\n", - "23.4) Как просмотреть список сохраненных стэшей?\n", - " - git stash list \n", - " git команда (подсказка: https://t.me/c/1937296927/3602/19531):\n", - "23.5) Как удалить стэш? \n", - " Команды для удаления отдельных стэшей или всех сразу.\n", - " git команда (подсказка: https://t.me/c/1937296927/3602/19531):\n", - " - git stash drop\n", - "23.6) Практические примеры использования стэша. \n", - " Краткие сценарии, где стэш помогает.\n", - " - Срочное переключение задачи\n", - " - Экспериментальные изменения\n", - " - Создание новой ветки из stash\n", - " - Переключение веток\n", - "24) Где посмотреть что есть конфликт в файлах?\n", - " - Github Desktop восклицательный знак напротив файла\n", - "24.1) Когда он появляется?\n", - " - Когда несколько пользователей хотят сохранить изменения в одном файле и гит не понимает какие изменения сохранить\n", - "25) Как решить конфликт в файлах?\n", - " 1. Stash changes (сохраняем локальные изменения) \n", - " 2. Fetch origin (принимаем изменения из интернета)\n", - " 3. Stashed changes - restore (восстанавливаем локальные изменения)\n", - " 4. Open in VSCode\n", - " 5. Решаем конфликт\n", - " 6. Resolve in merge editor \n", - " 7. Complete merge\n", - "26) Напишиие правильное утверждение\n", - "-Зелёное то что пришло с гитхаба и синее локальные изменения или синее то что пришло с гитхаба и зелёное это локальные изменения \n", - " - Зеленое гитхаб, синие локальные изменения\n", - "27) Если мы работаем в одном файле, можно ли принять pull после того как вы спрячете в стэш свои изменения? \n", - " - Да\n", - "27.1) Что может произойти когда stash восстановите после принятия pull?\n", - " - git уведомит о конфликте\n", - "28) Сколько способов решения конфликтов было показано в видео? Напишите ЧИСЛО и укажите их способы.\n", - " 1. Принять изменения с гитхаба\n", - " 2. Принять локальные изменения\n", - " 3. Оба изменения\n", - " 4. Ручные правки \n", - "29) Что делает кнопка complete merge?\n", - " - Завершить слияние (конфликт)\n", - "30) В какой чат нужно писать если остались вопросы?\n", - " - help me\n", - "31) Что такое FORK? Зачем его делают? \n", - " - Копирование репозитория\n", - "32) Как скачать форкнутый репозиторий на локальный компьютер?\n", - " - Github desktop - clone repository - clone\n", - "33) С какой вероятностью ваши ошибки были уже решены? и кто их решил?\n", - " - 99%. Преподаватель или студенты\n", - "34) Как создать файл в vs code?\n", - " - File - New file\n", - "35) Файл лога нужно заполнять в конце каждого урока?\n", - " - Да\n", - "==================\n", - "\n", - "Дополнительные вопросы:\n", - "1)Какая команда конвертирует файл в py из ipynb? \n", - "подсказка https://t.me/c/1937296927/1/26527 \n", - " - jupyter nbconvert --to script notebook.ipynb\n", - "2) Что такое пакетный менеджер? Вы пользуетесь пакетным менеджером conda или pip? Какой лучше использовать для дата сайнс?\n", - " - Программа, которая автоматизируют установку, обновление, настройку и удаление программного обеспечения и библиотек. Conda\n", - "3) Почему расширение py лучше чем ipynb?\n", - " - .py файлы удобно использовать при контроле версий с помощью Git, так как они являются обычными текстовыми файлами\n", - "4) Что такое pep8? \n", - "подсказка:https://peps.python.org/pep-0008/\n", - " - PEP 8 — это официальный стиль написания кода для языка Python\n", - "4.1) линтеры проверяют на соблюдение pep8?\n", - " - Да\n", - "4.2) Какая нотация используется для создания переменных? \n", - "ответ на 85-95 страницы https://t.me/c/1937296927/1/16676\n", - " - snake_case\n", - "4.3) Может ли переменная состоять из одной буквы например андерскор \"_\" ?\n", - " - да\n", - "4.4) Зачем и где мы используем андерскор _ \n", - " - Игнорирование значений\n", - " - Для обозначения внутренних/приватных переменных\n", - " - Как часть \"магических\" методов Python (init)\n", - "4.5) По PEP8 допустима переменная в одну букву?\n", - "ответ на 85-95 страницы https://t.me/c/1937296927/1/16676\n", - " - да" - ] - }, - { - "cell_type": "markdown", - "id": "d152b6f8", - "metadata": {}, - "source": [ - "1. Как включить автосохранение данных в VSCODE?\n", - " - File - autosave\n", - "2. Как настроить перенос строки? \n", - " - Preferences - settings - wordwrapcolumn\n", - "3. Сколько символов по pep8 разрешено на строке?\n", - " - 79\n", - "4. Какие способы переноса строк показаны в видео:\n", - " - 4.4, 4.6\n", - "4.1 Строки с использованием обратного слэша (\\)\n", - "\n", - "string_continued = \"This is a long string that we want to \" \\\n", - " \"split across multiple lines.\"\n", - "print(string_continued)\n", - "\n", - "4.2 Тройные кавычки (''' или \"\"\") \n", - "\n", - "multi_line_string = \"\"\"This is a string that spans\n", - "multiple lines. You can write freely\n", - "and it will keep the line breaks.\"\"\"\n", - "print(multi_line_string)\n", - "\n", - "4.3 Создание списка строк и объединение с помощью join\n", - "\n", - "strings = [\n", - " \"This is the first line.\",\n", - " \"This is the second line.\",\n", - " \"This is the third line.\"\n", - "]\n", - "result = \"\\n\".join(strings) # Используем перенос строк '\\n'\n", - "print(result)\n", - "\n", - "4.4 Использование круглых скобок для продолжения строки\n", - "long_string = (\n", - " \"This is a very long string that I would like to \"\n", - " \"continue on the next line.\"\n", - ")\n", - "print(long_string)\n", - "\n", - "4.5 Форматированные строки (f-строки) с использованием скобок\n", - "letter_a = 5\n", - "letter_b = 6\n", - "product_ab = letter_a * letter_b\n", - "\n", - "message = (\n", - " f\"when {letter_a} is multiplied by {letter_b}, \"\n", - " f\"the result is {product_ab}\"\n", - ")\n", - "print(message)\n", - "\n", - "4.6 Сложение строк с помощью +\n", - "\n", - "string_part1 = \"This is the first part, \"\n", - "string_part2 = \"and this is the second part.\"\n", - "full_string = string_part1 + string_part2\n", - "print(full_string)\n", - "\n", - "5. Проверка на ошибки c помощью кнопки problems, где она находится?\n", - " - Нижняя панель - Problems\n", - "6. Где в vscode находится клиент гита? как в нём отправить коммит? как принять домашку?\n", - " - Слева три кружочка\n", - " - Source control - Commit and push\n", - " - Домашка принимается кнопкой Pull\n", - "7. Что такое GIT? он локальный? В нём можно посмотреть историю изменений файлов и вернуться к любому коммиту?\n", - " - Git - система контроля версий\n", - " - Локальный\n", - " - Да\n", - "8. Как вставить картинку в маркдаун?\n", - " - Ctrl+V\n", - "9. Где посмотреть длину строки в vs code?\n", - " - Внизу окна VS Code в строке состояния\n", - "10. Как поменять тип ячейки с питона на маркдаун?\n", - " - В ячейке справа снизу\n", - "11. Как запустить сразу все ячейки в юпитере?\n", - " - Run all наверху\n", - "12. Как изменить размер картинки в юпитере? Нужно для этого знать HTML?\n", - " - \n", - " - Да\n", - "13. Какой хоткей чтобы запустить ячейку с смещением на следующую?\n", - " - Shift+enter\n", - "14. Как включить отображение номеров строк в юпитере(Cell line numbers)?\n", - " - Show cell line numbers или клавиша L\n", - "15. Что такое \"Go To\" чем это полезно? Как перейти сразу на ошибочную ячейку?\n", - " - Перейти на ячейку с ошибкой\n", - " - Нажать на \"Go To\"\n", - "16. Как очистить вывод ячеек которые уже запущены?\n", - " - Clear all outputs\n", - "17. Как работать одновременно в нескольких файлах в VSCODE? Что такое SPLIT?\n", - " - Split edit\n", - " - Разделение редактора на несколько панелей для работы с разными файлами одновременно\n", - "18. Каким сочетанием убирается левый сайдбар?\n", - " - Ctrl+B\n", - "19. Кнопка два листочка это наши локальные файлы?\n", - " - Да\n", - "20. Какая ошибка появилась в трассировке при запуске всех ячеек DICT или LIST?\n", - " - Name 'Dict' in not defined\n", - "21. Вы ознакомились с https://t.me/c/1937296927/832/19307? и https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet?\n", - " - Да\n", - "22. Что такое валидация?\n", - " - Проверка кода на правильность\n", - "23. Что такое трассировка ошибки?\n", - " - Текстовое представление ошибки\n", - "24. Что значит отвалился интерпритатор?\n", - " - Интерпритатор завершил работу из-за ошибки" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv (3.14.0)", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.14.0" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/quiz.py b/quiz.py deleted file mode 100644 index f393b5fb..00000000 --- a/quiz.py +++ /dev/null @@ -1,273 +0,0 @@ -"""SENATOROVAI/intro-cs/issues/6.""" - -# 1) Как понять, что домашка пришла? -# - В чате homework будет сообщение прими пул. -# 2) Как принять домашку? -# - Заходим в Github desktop, затем fetch origin -# - Нажимаем history, смотрим ходим в самый свежий коммит -# 3) Зачем нужна кнопка history и какие функции появляются при нажатии правой кнопки мыши на коммит? -# - History - просмотр истории коммитов -# - Amend commit - изменение последнего коммита -# - Reset to commit - отменяет все коммиты, которые идут после укзанного коммита -# - Checkout commit – перемещение по коммитам -# - Reorder commit – изменить порядок коммитов -# - Revert changes in commit – создает новый коммит, который отменяет изменения, внесенные в выбранном коммите, не удаляя сам коммит из истории -# - Create tag... – создает тег для пометки определенного коммита -# - Create branch from commit – позволяет создать новую ветку, начиная с выбранного коммита -# - Cherry-pick commit – копирует коммит из одной ветки в другую -# - Copy SHA – копирует уникальный идентификатор коммита -# - View on GitHub – открывает выбранный коммит на сайте GitHub -# - Copy tag - копирует имя выбранного тега -# 3.1) Где брать ссылку на коммит? куда её отправлять? -# - GitHub вкладка коммит -# - Review запросы? -# 4) Что такое файл лога? -# - Файл для записи прогресса в учебе -# 4.1) Когда нужно его пушить? -# - Когда сделали домашку -# 5) Что такое интерпритатор? -# - Программа, которая читает и выполняет код -# 6) Где можно выбрать интерпритатор? -# - base -# - Select kernel -# - или Ctrl + Shift + P, затем Python: Select Interpreter -# 7) Что такое модуль? -# - Файл с расширением .py, который содержит код и может быть повторно использован в других программах -# 8) Как создать и отправить коммит? -# - GitHub Desktop - название коммита - commit to main - push origin -# 9) Как посмотреть что коммит точно отправлен и находится в github? -# - History - commit - view on github -# 10) Какая команда показывает что код не прошёл проверки на ошибки? -# - pre-commit run --all-files -# 10.1) Напишите список линтеров которые используются для проверки кода и дайте их краткую характеристику. -# - black — автоформаттер Python кода согласно единому стилю PEP8 -# - flake8 — для проверки стиля и потенциальных ошибок -# - mypy — анализатор для проверки соответствия типов -# - nbqa-pydocstyle — проверяет соответствие docstring документации в notebook-коде внутренним стандартам -# - codespell — ищет орфографические ошибки и опечатки в коде -# - pylint — проверяет стиль, ошибки и сложность кода -# 11) Как узнать какой именно линтер не прошёл проверку? -# - Failed напротив линтера, который не прошел проверку -# 12) Линтер Pylint видит markdown? -# - нет -# 13) Номер ячейки в терминале и номер ячейки в vs code может отличаться? в каком случае? -# - да, линтер не видит markdown и номер ячейки может при проверке линтера может отличаться -# 14) Где посмотреть номер ячейки в vscode? -# - внизу справа -# 15) В каком формате ipynb отправляется в гитхаб? причём здесь JSON? -# - GitHub принимает файлы .ipynb в формате JSON. Jupyter Notebook с расширением .ipynb представляет файл в формате JSON. -# 16) Где посмотреть в какой ячейке ошибка? -# - В терминале после запуска pre-commit run --all-files сообщение об ошибке и номере ячейки -# 17) Как запустить терминал? -# - View - Terminal -# - Ctrl+` -# 18) Что такое линтер? -# - Программа, которая проверяет код на наличие ошибок и несоответствий стандартам написания кода -# 19) В какой сайт нужно вставлять код ошибки если ошибка связана с pylint? -# - https://pylint.readthedocs.io/en/stable/ -# 20) Секция pydocstyle в большинстве случае автоматический закрывается после исправления ошибок в каком линтере? -# - pylint -# 21) Что такое описание модуля? Оно должно отражать информацию о том что находится в модуле? -# - Описание файла. Да -# 21.1) С какой git команды начинается утро программиста? -# - git pull, git status -# 22) После внесения изменений в файлах, кнопка open in vs code пропадает в кошке, как по другому открыть vs code из кошки? -# - current repository - open in VSCode -# 23) Что такое stash? -# Общее объяснение концепции. -# - Команда в Git, которая позволяет временно сохранить все сделанные, но ещё не зафиксированные изменения в коде -# 23.1) Как сохранить стэш? -# git командa(подсказка: https://t.me/c/1937296927/3602/19531): -# - git stash -# Кнопка в vs code: Ctrl + Shift + G -# 23.2) Как восстановить стэш(подсказка: https://t.me/c/1937296927/3602/25747)?: -# - git stash apply -# git команда(подсказка: https://t.me/c/1937296927/3602/19531)?: -# 23.3) Различие между стэшем и коммитом. -# Когда лучше сохранить изменения в стэше, а когда коммитить. -# - Git commit — для постоянного сохранения изменений -# - Git stash — для временного сохранения изменений -# - Git commit: изменения полностью готовы; для общего репозитория и совместной работы -# - Git stash: когда надо переключить на другую ветку, а текущий код не готов; неполные изменения; если нужно отложить работу -# 23.4) Как просмотреть список сохраненных стэшей? -# - git stash list -# git команда (подсказка: https://t.me/c/1937296927/3602/19531): -# 23.5) Как удалить стэш? -# Команды для удаления отдельных стэшей или всех сразу. -# git команда (подсказка: https://t.me/c/1937296927/3602/19531): -# - git stash drop -# 23.6) Практические примеры использования стэша. -# Краткие сценарии, где стэш помогает. -# - Срочное переключение задачи -# - Экспериментальные изменения -# - Создание новой ветки из stash -# - Переключение веток -# 24) Где посмотреть что есть конфликт в файлах? -# - Github Desktop восклицательный знак напротив файла -# 24.1) Когда он появляется? -# - Когда несколько пользователей хотят сохранить изменения в одном файле и гит не понимает какие изменения сохранить -# 25) Как решить конфликт в файлах? -# 1. Stash changes (сохраняем локальные изменения) -# 2. Fetch origin (принимаем изменения из интернета) -# 3. Stashed changes - restore (восстанавливаем локальные изменения) -# 4. Open in VSCode -# 5. Решаем конфликт -# 6. Resolve in merge editor -# 7. Complete merge -# 26) Напишиие правильное утверждение -# -Зелёное то что пришло с гитхаба и синее локальные изменения или синее то что пришло с гитхаба и зелёное это локальные изменения -# - Зеленое гитхаб, синие локальные изменения -# 27) Если мы работаем в одном файле, можно ли принять pull после того как вы спрячете в стэш свои изменения? -# - Да -# 27.1) Что может произойти когда stash восстановите после принятия pull? -# - git уведомит о конфликте -# 28) Сколько способов решения конфликтов было показано в видео? Напишите ЧИСЛО и укажите их способы. -# 1. Принять изменения с гитхаба -# 2. Принять локальные изменения -# 3. Оба изменения -# 4. Ручные правки -# 29) Что делает кнопка complete merge? -# - Завершить слияние (конфликт) -# 30) В какой чат нужно писать если остались вопросы? -# - help me -# 31) Что такое FORK? Зачем его делают? -# - Копирование репозитория -# 32) Как скачать форкнутый репозиторий на локальный компьютер? -# - Github desktop - clone repository - clone -# 33) С какой вероятностью ваши ошибки были уже решены? и кто их решил? -# - 99%. Преподаватель или студенты -# 34) Как создать файл в vs code? -# - File - New file -# 35) Файл лога нужно заполнять в конце каждого урока? -# - Да -# ================== -# -# Дополнительные вопросы: -# 1)Какая команда конвертирует файл в py из ipynb? -# подсказка https://t.me/c/1937296927/1/26527 -# - jupyter nbconvert --to script notebook.ipynb -# 2) Что такое пакетный менеджер? Вы пользуетесь пакетным менеджером conda или pip? Какой лучше использовать для дата сайнс? -# - Программа, которая автоматизируют установку, обновление, настройку и удаление программного обеспечения и библиотек. Conda -# 3) Почему расширение py лучше чем ipynb? -# - .py файлы удобно использовать при контроле версий с помощью Git, так как они являются обычными текстовыми файлами -# 4) Что такое pep8? -# подсказка:https://peps.python.org/pep-0008/ -# - PEP 8 — это официальный стиль написания кода для языка Python -# 4.1) линтеры проверяют на соблюдение pep8? -# - Да -# 4.2) Какая нотация используется для создания переменных? -# ответ на 85-95 страницы https://t.me/c/1937296927/1/16676 -# - snake_case -# 4.3) Может ли переменная состоять из одной буквы например андерскор "_" ? -# - да -# 4.4) Зачем и где мы используем андерскор _ -# - Игнорирование значений -# - Для обозначения внутренних/приватных переменных -# - Как часть "магических" методов Python (init) -# 4.5) По PEP8 допустима переменная в одну букву? -# ответ на 85-95 страницы https://t.me/c/1937296927/1/16676 -# - да - -# 1. Как включить автосохранение данных в VSCODE? -# - File - autosave -# 2. Как настроить перенос строки? -# - Preferences - settings - wordwrapcolumn -# 3. Сколько символов по pep8 разрешено на строке? -# - 79 -# 4. Какие способы переноса строк показаны в видео: -# - 4.4, 4.6 -# 4.1 Строки с использованием обратного слэша (\) -# -# string_continued = "This is a long string that we want to " \ -# "split across multiple lines." -# print(string_continued) -# -# 4.2 Тройные кавычки (''' или """) -# -# multi_line_string = """This is a string that spans -# multiple lines. You can write freely -# and it will keep the line breaks.""" -# print(multi_line_string) -# -# 4.3 Создание списка строк и объединение с помощью join -# -# strings = [ -# "This is the first line.", -# "This is the second line.", -# "This is the third line." -# ] -# result = "\n".join(strings) # Используем перенос строк '\n' -# print(result) -# -# 4.4 Использование круглых скобок для продолжения строки -# long_string = ( -# "This is a very long string that I would like to " -# "continue on the next line." -# ) -# print(long_string) -# -# 4.5 Форматированные строки (f-строки) с использованием скобок -# letter_a = 5 -# letter_b = 6 -# product_ab = letter_a * letter_b -# -# message = ( -# f"when {letter_a} is multiplied by {letter_b}, " -# f"the result is {product_ab}" -# ) -# print(message) -# -# 4.6 Сложение строк с помощью + -# -# string_part1 = "This is the first part, " -# string_part2 = "and this is the second part." -# full_string = string_part1 + string_part2 -# print(full_string) -# -# 5. Проверка на ошибки c помощью кнопки problems, где она находится? -# - Нижняя панель - Problems -# 6. Где в vscode находится клиент гита? как в нём отправить коммит? как принять домашку? -# - Слева три кружочка -# - Source control - Commit and push -# - Домашка принимается кнопкой Pull -# 7. Что такое GIT? он локальный? В нём можно посмотреть историю изменений файлов и вернуться к любому коммиту? -# - Git - система контроля версий -# - Локальный -# - Да -# 8. Как вставить картинку в маркдаун? -# - Ctrl+V -# 9. Где посмотреть длину строки в vs code? -# - Внизу окна VS Code в строке состояния -# 10. Как поменять тип ячейки с питона на маркдаун? -# - В ячейке справа снизу -# 11. Как запустить сразу все ячейки в юпитере? -# - Run all наверху -# 12. Как изменить размер картинки в юпитере? Нужно для этого знать HTML? -# - -# - Да -# 13. Какой хоткей чтобы запустить ячейку с смещением на следующую? -# - Shift+enter -# 14. Как включить отображение номеров строк в юпитере(Cell line numbers)? -# - Show cell line numbers или клавиша L -# 15. Что такое "Go To" чем это полезно? Как перейти сразу на ошибочную ячейку? -# - Перейти на ячейку с ошибкой -# - Нажать на "Go To" -# 16. Как очистить вывод ячеек которые уже запущены? -# - Clear all outputs -# 17. Как работать одновременно в нескольких файлах в VSCODE? Что такое SPLIT? -# - Split edit -# - Разделение редактора на несколько панелей для работы с разными файлами одновременно -# 18. Каким сочетанием убирается левый сайдбар? -# - Ctrl+B -# 19. Кнопка два листочка это наши локальные файлы? -# - Да -# 20. Какая ошибка появилась в трассировке при запуске всех ячеек DICT или LIST? -# - Name 'Dict' in not defined -# 21. Вы ознакомились с https://t.me/c/1937296927/832/19307? и https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet? -# - Да -# 22. Что такое валидация? -# - Проверка кода на правильность -# 23. Что такое трассировка ошибки? -# - Текстовое представление ошибки -# 24. Что значит отвалился интерпритатор? -# - Интерпритатор завершил работу из-за ошибки From 1aab7ab235c2b991cb88f83d814a27d4b5bbe09a Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sun, 26 Oct 2025 13:10:41 +0300 Subject: [PATCH 05/37] =?UTF-8?q?[TASK]=20=D0=92=D0=B8=D1=80=D1=82=D1=83?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BE=D0=BA=D1=80=D1=83?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=20#7=20(https://github.com/SE?= =?UTF-8?q?NATOROVAI/intro-cs/issues/7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/SENATOROVAI/intro-cs/issues/7 --- python/venv.ipynb | 112 ++++++++++++++++++++++++++++++++++++++++++++++ python/venv.py | 66 +++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 python/venv.ipynb create mode 100644 python/venv.py diff --git a/python/venv.ipynb b/python/venv.ipynb new file mode 100644 index 00000000..a44c2d17 --- /dev/null +++ b/python/venv.ipynb @@ -0,0 +1,112 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "7fa44870", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"[TASK] Виртуальное окружение #7.\"\"\"" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "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" + }, + "image-3.png": { + "image/png": "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" + }, + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "id": "be9cf136", + "metadata": {}, + "source": [ + "1. Что делает команда python -m venv venv?\n", + " - Создает виртуальное окружение в папке venv\n", + "\n", + "1.1 Что делают команды:\n", + "\n", + " - pip list - отображает список установленных модулей в текущем окружении\n", + " - pip freeze > requirements.txt - выгрузка установленных модулей в текущем окружении в файл requirements.txt\n", + " - pip install -r requirements.txt - установка модулей, записанных в файле requirements.txt\n", + "\n", + "2. Что делают команды:\n", + " - conda env list - показывает список виртуальных сред conda\n", + " - conda create -n env_name python=3.5 - создает виртуальную среду с именем env_name и версией питона 3.5\n", + " - conda env update -n env_name -f file.yml - настройка виртуальной среды из файла file.yml\n", + " - source activate env_name (conda activate env_name) - переключение на виртуальную среду env_name\n", + " - source deactivate (conda deactivate) - отключение виртуальной среды\n", + " - conda clean -a - очистка кэша conda\n", + "\n", + "3. Вставьте скрин вашего терминала, где вы активировали сначала venv, потом conda, назовите окружение \"SENATOROV\"\n", + "\n", + " ![image.png](attachment:image.png)\n", + "\n", + "4. Как установить необходимые пакеты внутрь виртуального окружения для conda/venv? \n", + " 1. Активировать окружение\n", + " 2. Установить пакеты через conda/pip install\n", + " 3. Для venv можно загрузить пакеты из фала зависимостей pip install -r requirements.txt\n", + "\n", + "5. Что делают эти команды?\n", + " - pip freeze > requirements.txt - выгрузка зависимостей в requirements.txt\n", + " - conda env export > environment.yml - экспорт виртуального окружения в environment.yml\n", + "\n", + "5.1 вставьте скрин, где будет видна папка VENV в вашем репозитории, а также файлы зависимостей requirements.txt и environment.yml, файлы должны содержать зависимости\n", + " ![image-2.png](attachment:image-2.png)\n", + "\n", + "6. Что делают эти команды?\n", + " - pip install -r requirements.txt - устанавливает пакеты из requirements.txt\n", + " - conda env create -f environment.yml - создает виртуальную среду из environment.yml\n", + "\n", + "7. Что делают эти команды?\n", + " - pip list - выводит список всех установленных пакетов в виртуальном окружении\n", + " - pip show имя пакета - показывает подробную информацию об указанном пакете\n", + " - conda list - показывает список всех установленных пакетов в conda\n", + "\n", + "8. Где по умолчанию больше пакетов venv/pip или conda? и почему дата сайнинисты используют conda?\n", + " - pip имеет доступ к большему количеству пакетов\n", + " - в conda есть предварительно собранные пакеты для работы в ДС и она более удобная\n", + "\n", + "9. Вставьте скрин где будет видно, Выбор интерпретатора Python (conda) в VS Code/cursor\n", + "\n", + " ![image-3.png](attachment:image-3.png)\n", + "\n", + "10. Добавьте в .gitignore папку SENATOROV\n", + " - echo SENATOROV/ >> .gitignore\n", + "\n", + "11. Зачем нужно виртуальное окружение?\n", + " - Чтобы пакеты и их различные версии не конфликтовали друг с другом в разных проектах\n", + " - Удобство\n", + " - Для тестирования разных версий и конфигураций\n", + "\n", + "12. С этого момента надо работать в виртуальном окружении conda, ты научился(-ась) выгружать зависимости и работать с окружением?\n", + " - ✓\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "4b78996d", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/venv.py b/python/venv.py new file mode 100644 index 00000000..aa8bf1a9 --- /dev/null +++ b/python/venv.py @@ -0,0 +1,66 @@ +"""[TASK] Виртуальное окружение #7.""" + +# 1. Что делает команда python -m venv venv? +# - Создает виртуальное окружение в папке venv +# +# 1.1 Что делают команды: +# +# - pip list - отображает список установленных модулей в текущем окружении +# - pip freeze > requirements.txt - выгрузка установленных модулей в текущем окружении в файл requirements.txt +# - pip install -r requirements.txt - установка модулей, записанных в файле requirements.txt +# +# 2. Что делают команды: +# - conda env list - показывает список виртуальных сред conda +# - conda create -n env_name python=3.5 - создает виртуальную среду с именем env_name и версией питона 3.5 +# - conda env update -n env_name -f file.yml - настройка виртуальной среды из файла file.yml +# - source activate env_name (conda activate env_name) - переключение на виртуальную среду env_name +# - source deactivate (conda deactivate) - отключение виртуальной среды +# - conda clean -a - очистка кэша conda +# +# 3. Вставьте скрин вашего терминала, где вы активировали сначала venv, потом conda, назовите окружение "SENATOROV" +# +# ![image.png](attachment:image.png) +# +# 4. Как установить необходимые пакеты внутрь виртуального окружения для conda/venv? +# 1. Активировать окружение +# 2. Установить пакеты через conda/pip install +# 3. Для venv можно загрузить пакеты из фала зависимостей pip install -r requirements.txt +# +# 5. Что делают эти команды? +# - pip freeze > requirements.txt - выгрузка зависимостей в requirements.txt +# - conda env export > environment.yml - экспорт виртуального окружения в environment.yml +# +# 5.1 вставьте скрин, где будет видна папка VENV в вашем репозитории, а также файлы зависимостей requirements.txt и environment.yml, файлы должны содержать зависимости +# ![image-2.png](attachment:image-2.png) +# +# 6. Что делают эти команды? +# - pip install -r requirements.txt - устанавливает пакеты из requirements.txt +# - conda env create -f environment.yml - создает виртуальную среду из environment.yml +# +# 7. Что делают эти команды? +# - pip list - выводит список всех установленных пакетов в виртуальном окружении +# - pip show имя пакета - показывает подробную информацию об указанном пакете +# - conda list - показывает список всех установленных пакетов в conda +# +# 8. Где по умолчанию больше пакетов venv/pip или conda? и почему дата сайнинисты используют conda? +# - pip имеет доступ к большему количеству пакетов +# - в conda есть предварительно собранные пакеты для работы в ДС и она более удобная +# +# 9. Вставьте скрин где будет видно, Выбор интерпретатора Python (conda) в VS Code/cursor +# +# ![image-3.png](attachment:image-3.png) +# +# 10. Добавьте в .gitignore папку SENATOROV +# - echo SENATOROV/ >> .gitignore +# +# 11. Зачем нужно виртуальное окружение? +# - Чтобы пакеты и их различные версии не конфликтовали друг с другом в разных проектах +# - Удобство +# - Для тестирования разных версий и конфигураций +# +# 12. С этого момента надо работать в виртуальном окружении conda, ты научился(-ась) выгружать зависимости и работать с окружением? +# - ✓ +# +# + +# From 632b25b06cfe8df4f906d4c9fef778feae9ae9ce Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 27 Oct 2025 13:59:55 +0300 Subject: [PATCH 06/37] fix: correct number rounding function From 134e1df41fa119e607c8a7cc23a6f666c877e3b3 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 27 Oct 2025 14:05:31 +0300 Subject: [PATCH 07/37] feat: add generateReport function for generating reports From aa2f1d64631f8204c5bf5ce58c7cc318fdd53c32 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 27 Oct 2025 14:06:13 +0300 Subject: [PATCH 08/37] style: fixed indents and formatting throughout the project From e94ffe1ff1f2cedcc4e91650beb6a6fd49497899 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 27 Oct 2025 14:06:45 +0300 Subject: [PATCH 09/37] docs: add documentation for the generateReport function From 2c575c6ab5e6bebde5e645469c894198194d1dee Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 27 Oct 2025 14:07:20 +0300 Subject: [PATCH 10/37] test: add tests for the generateReport function From db4c7efe8d1750be6b932f25639187b324e12219 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 27 Oct 2025 14:30:56 +0300 Subject: [PATCH 11/37] [TASK] Commits #5 (https://github.com/SENATOROVAI/intro-cs/issues/5) Closes https://github.com/SENATOROVAI/intro-cs/issues/5 --- python/commits.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 python/commits.py diff --git a/python/commits.py b/python/commits.py new file mode 100644 index 00000000..3ff71da2 --- /dev/null +++ b/python/commits.py @@ -0,0 +1,31 @@ +"""[TASK] Commits #5.""" + +# Опишите своими словами назначение каждого из этих типов коммитов: +# feat - добавление нового функционала +# fix - исправление бага +# docs - изменения в документации +# style - исправление оформления кода +# refactor - исправление или улучшение кода без добавления функционала +# test - добавление и изменение тестов +# build - изменение системы сборки или внешних зависимостей +# ci - изменения в файлах настройки непрерывной интеграции (CI) +# perf - улучшение производительности без изменения поведения +# chore - регулярное обслуживание или административные задачи +# +# Представьте, что вы исправили баг в функции, которая некорректно округляет числа. Сделайте фиктивный коммит и напишите для него сообщение в соответствии с Conventional Commits (используя тип fix). +# git commit --allow-empty -m "fix: correct number rounding function" +# +# Добавление новой функциональности: +# Допустим, вы реализовали новую функцию generateReport в проекте. Сделайте фиктивный коммит с типом feat, отражающий добавление этой функциональности +# git commit --allow-empty -m "feat: add generateReport function for generating reports" +# +# Модификация формата кода или стилей: +# Представьте, что вы поправили отступы и форматирование во всём проекте, не меняя логики кода. Сделайте фиктивный коммит с типом style +# git commit --allow-empty -m "style: fixed indents and formatting throughout the project" +# +# Документация и тестирование: +# +# Сделайте фиктивный коммит с типом docs, добавляющий или улучшающий документацию для вашей новой функции. +# git commit --allow-empty -m "docs: add documentation for the generateReport function" +# Сделайте фиктивный коммит с типом test, добавляющий тесты для этой же функции. +# git commit --allow-empty -m "test: add tests for the generateReport function" From 3b8c34d749b400742ebf8b653e77239c35c9ddb1 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sat, 1 Nov 2025 13:48:46 +0300 Subject: [PATCH 12/37] [TASK] issues #2 (https://github.com/SENATOROVAI/intro-cs/issues/2) Closes https://github.com/SENATOROVAI/intro-cs/issues/2 --- git/stash.ipynb | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ git/stash.py | 38 ++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 git/stash.ipynb create mode 100644 git/stash.py diff --git a/git/stash.ipynb b/git/stash.ipynb new file mode 100644 index 00000000..2c3c940a --- /dev/null +++ b/git/stash.ipynb @@ -0,0 +1,93 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2bea6fdf", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"[TASK] STASH #3.\"\"\"\n" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "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" + }, + "image-3.png": { + "image/png": "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" + }, + "image-4.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAqsAAABMCAIAAADFp2m1AAAZaUlEQVR4Ae2cTXLbSBKF5xhotRwdnoW98HEU4Z7w+CRuy2NfYjyUem81rStYorcSDjVBPPLpKTMBghQlkc2HhaJYqMqfL7OqEiDtfzS+TMAETMAETMAEDo/APw7PZXtsAiZgAiZgAibQuAJwEpiACZiACZjAIRJwBXCIUbfPJmACJmACJuAKwDlgAiZgAiZgAodIwBXAIUbdPpuACZiACZiAKwDngAmYgAmYgAkcIgFXAIcYdftsAiZgAiZgAq4AnAMmYAImYAImcIgEXAEcYtTtswmYgAmYgAm4AnAOmIAJmIAJmMAhEnAFcIhRt88mYAImYAIm4ArAOWACJmACJmACh0jAFcAhRt0+m4AJmIAJmMDWKoAv05sfk3cjgb789Y/Ltr2dnZ8cHY2cUg5bS2nTNMe/nPw5ux1vZ6l0Bzu3xXPYtXVpD0t71LtPA+RRXdiu8N0E8ub0ol1cs/++/ed2XX52adhtvp2+fnZLdsqAnUrFh8fo/eSq7a6b6eed4jzSmKEKAKFCBoPUwJn96u3X6+pEJ6C2vVvkZRJscMCsO2WgAlhX1Ei+TzNsWzyHrc0hlh28bdt2ZWn1EMjwca6mu4Y31hJI8ySXJPzc0JVMVhoVHN9so3lGIH0OwqThOPbNffb+MZm81ukSogwsX6Y3rVw/Ju9evf36s22ZA29OL7gn4xY/hrXZtu1te/nh+AWsglQM1p5WhGv/7ewcH0M+j+EQglWm4gZyVOzG0+HUw5Pw/eSKQVHDdr89tgJ4P7libpVelSi/TG+Qdnj+nkwnAw/9G0Rx3SllEsOddUWVEHaqc+se5RDPN6BuW2maBhtQ2CACkI1NGghcUPHsH7e+F+hJqe1n9/SBBrx6+3XWfv9w/OKBcp5l+phMzuulz9S+9M5aOmiz69mCm1YA7ydXV5P/nM+u9W1KkKwf0b6ZfkYDpyAS7MfkXTkynAIvf/3j++38ue7N6cUDz7/saR+rsn/j6ep7KXlk59ZX/Ui9Dx82qgLIaxWJEqKu6YgjYSbP/bSVD0mczp52efEWZ7GBmC0HLp6xtJNztVPrX5xScOG2vezTjgFQxGL5fHb99ffTy66Xhx9t0waSksKxwNQk2Hn8y0kpM2vntxgLk5ZHL1XQcfZgJOv6Uma2U8/1pmn0Ywix3tKR6ibQ9ZmURypDtrHx6dbGW/qsg8hSF4EEdMPk4QjRQabaqWJpBhvlXjCSPIVoA3P5mMLNrjRJFbXza75BZyBl1pWdJbqmaXLahJHDq0Ozhc6WMtUj5FJ33pz9Obtt29nk0/9+Dn6fqJTUpJw2pfZ707t3nITZ4Z3/YT4EmZh78enTyu2iL70ZayLCVnw2uUZacj1C17fT1+8nV7iFKehnD8fjLqRxLiP4Y/KuHBns5BhICI+IGrg5pp5UHOAJI/UvFHXSFq80+qbfG7l8OX2vU96IjIkRzAghpm1h1asipod2klWZdRT7BI3VFcDF6b9CXdk0DaJL32Doy2VJiI+hYAzOBGTcU8Kw/PHL9IZ6uULYCcrI+KyCd2E/F0bWzpFcFSyW+XUGlWYjIZC7A1FwCuWjEWTyrmpvmiZ7RNX5FuFgTJ9MvGkEUtgJOHrkEHgI8XwLWBYiWvP9e/IXTmtVmiE3TdM3kn6p8aTEu92CvPt2if2Z1TB5tbOUmaerLm3nQKhwtMEzky/fkGk4lH9pEjvxSoZBDEBgBnjSvLITUYPBHNmX3tl3JcN2366dgahGtG+mn7ELfzt9jVeMH3/7iCdRytdGaVJfiPOC7cvPsLhQ0IT87OOp5qGtI/Vu1oIz++NvH6fXZydHRzyD0f/h+AV7VDI3ukADqQXtSBWmzcBISgvm6RtfTRvKpGtBeLkzcLA28kTcDZb0bSx5upJHm96pXrTLtMGtIJnLUGWWuZRzvtwEsjHb6lldAaz1XaaC4LGHDJh/EbWsxcJ+BGdyFLOTTHSd0m2Ld68TuQbmBsj5xNP06uzsMn1BG7RXZ9t3XSpY8+pRsHa+Hpb+wuzSzlJmqf3D8YvsEZUqeYXDAX0ys51QhE2/s/neEauKgsw8OAQ6QKZtaKjkcIsD2u7CKtXVlcertDHkMb6UWU7vW6jhePt2+jpQYg6X5LMj2KbhOBdjaZLWCtkRBRKyTn1n0YDOUtHJ0VFp/EB+Zr8CFhwDYcmEMUA37+xWFtKps/BeiqqubFImg/GlR0EUS+GQyaXMErIKDG1mDg8hHA/t8vp2+lqTZ5FaHQoGN9AIhpVmY8xSyYJkORLvGAA/KIIvnQtzCeNTERODOnTmvzmaY6YTTp4+PkaBZLCNKvB4/P22OI90CsevzDqd9Rjt1RXAxadPAyV2sIkJiq2f67nBy+TliRgOBggZkwQqn5UjK4x2eVEvl5CeGd2ouGUE7dxlYBv0hnQJ2xNG8m8QyG/KO+2LP/x9DbddyCy14xvT4BHVMaXYEwzokxmGYTo552/4eGsRU6mxultzsCEifftmZoKROj3UcJxyM/2suwy9ZkOBqMC2uzJ5jC9lltNPjo60n3aqXhizLvmfMHH50y01CRl4M/2sqoNHZBi+g1PDQibjVtlZKkIFwIMKbuJvzk+VQEo5ebicVVqJjp1IXR5FfYqCScpTdfUtBIaDbwiyqaXMkicTuO0uBQJj4AWCmE3i6nv19utV9zokZ7LOgg2MlObA8qya0U4dXI7E0kZeMQrKEEUMX5eOSUVMV5tVYG6HaPZN12QIgWu7S08Ebr/Ba9VehpgDdGJQzUff0N8XYsp8msbqCiA/xAxbxliGdyYhYxQZBHLigHwuAJ0SOvN0BE9/3pKLwaB9bq2cbfjIpQIVwaOgNwjEys8/fSplltr1N1P0iEpX8uyTme3kyxK8aOXyoC5OCTJBNdimhnEiRA2MpK6yMVe0/HFyNg9TVG+ZIYE8xodOiCqnl4aVpW2gxI+BRp9AUKKb8+mz8z6TuD/ybQHFKpDgZuk7OgcU8VyhCjZCZNmvDXJgZwYSxuAjCLAKYQVAOWWDJgXfOThr5xSMUYBhcCkzdOp0Ki0bSG86qGMYDvxo42zyF5JByxQeOVzIjBTRQSakqZ0EXo7kPyX4MXkXCCxe4cieOTIVYUmWpl7ndghNmB7uZvIcoL6Xi5eqw0j2o6EqGCMdQ41hfLBcpzxNe1QFkB/o4Q+qmGAoUwfI+DjO/oCA05n37MkN6MVuiHqT53ppDCTAEo7EetDXL30Oap3I6dAeIppNzaGFGcFOTSzKZCOv4eAR9WoKojPw7JOZ7cT0Ll6z8l94MpTzxnLN89tZKAIlfaDpg1yOpF+5oRjnPi4NCCMViE7hMCWviz/LLKdTTmioXtxal3wQqDxhCVMx5FK59VCaGlb6PtAZFOWHYGpBg3aGfv2oyYP+nIolOqYfxo+sANSkHOLSIyU/nMmL3L6fiiVPJVC2MQvAMxAN8ZvTi9lsdjs7JxAI7MYs3nGq1/z91sC2xvElecjvNpZZeMMUtlO1M7upqSgy1/jvYWhnOX0gcBjP6WhgC9JNINtchpjD1CPIDEumz6QcYsp8msbYCgBecbeFP8FJWByYaiXI57ZWLgrBRNxhJ2TqX5w0eLHDH77qXDz9hB4IRKcuAP54R8djMHxUe3RMfsZSI8sNhcc5ZJZ2wjau1QHt6hEF6vsutbbPoz47aQDtUe8gGS+HRPXdFyv8RvO2vTybXEN7cB+dfSNVHd4Yi6K7X19jWfIWj0b2EIjSUPLl4qdVjHKeHizkR90L2JlzaYA8Z6Ghc+lOgEk7dbmpm61cfEkbfGdYoZeOlL7nnSsMY9AhLf8dUwEwD2E+ZPLAW1kBDJiUQ5w90uxamck6WMkHyJnDQHrnaOrJisS4nZ0Hy+G17nK6ijWddAzt5LvbPBLGoxjiox3sz+HOxodw9CVzFgW9YboO01voZ3wZOB1D1eik78z5Mkx9IW7lgvagC5yzSeM3gT57Ht4/VAFsLJ1LdGMJuzkxpMtWjHwMmVsxbPjR6u8a4q2gey4hejwsv3W6K8ueyyrrPUACTsV9CfqjVAD74vy6dj7Gaf0YMtf1qxzPf9BS3nXnDhIIT9Xlu+4dNNsm/f0IOBX3JaY7WgHoC6hWLn3z8/SI82n9cDuzzKf3K2jE6zt9yxcG+OPOEtBXr/zObmettWF/YwJOxb0I7o5WAHvBzkaagAmYgAmYwP4ScAWwv7Gz5SZgAiZgAiawOQFXAJuz80wTMAETMAET2F8CrgD2N3a23ARMwARMwAQ2J+AKYHN2nmkCJmACJmAC+0vAFcD+xs6Wm4AJmIAJmMDmBFwBbM7OM03ABEzABExgfwm4Atjf2NlyEzABEzABE9icgCuAzdl5pgmYgAmYgAnsLwFXAPsbO1tuAiZgAiZgApsTcAWwOTvPNAETMAETMIH9JeAKYH9jZ8tNwARMwARMYHMCrgA2Z+eZJmACJmACJrC/BFwB7G/sbLkJmIAJmIAJbE7AFcDm7DzTBEzABEzABPaXgCuA/Y2dLTcBEzABEzCBzQm4AticnWeagAmYgAmYwP4ScAWwv7Gz5SZgAiZgAiawOQFXAJuz80wTMAETMAET2F8CrgD2N3a23ARMwARMwAQ2J+AKYHN2nmkCJmACJmAC+0tgaxXAl+nNj8m7kSBe/vrHZdvezs5Pjo5GTimHraW0aZrjX07+nN2Ot7NUuoOd2+I57Nq6tIelPerdpwHyqC5sV/huAnlzetEurtl/3/5zuy4/uzTsNt9OXz+7JTtlwE6l4loxej+5arvrZvpZke6UR2rYmPZQBQDHkMEgNXBmv3r79bo60Umtbe8WeYlsgwNm3SkDFcC6osbAfbIx2+I5bHAOsezgbdu2K0urh0CGj3M13TW8sZZAmie5JOHnhq5kstKo4HjYfVZOx4BnBNJnIUwajmPf3GfvH5PJa50uIcrA8mV608r1Y/Lu1duvP9uWOfDm9IJ7Mm7xY1ibbdvetpcfjl/AKkjFYO1pRbj2387O8THk8xgOIVhlKm4gR8VuPB1OrZWE7ydX5A8bSo/UPLY3tpMStt4YWwG8n1wxt0ojSpRfpjdIOzx/T6aTgYf+DeisO6VMYrizrqgSwk51bt2jHOL5BtRtK03TYAMKG0QAsrFJA4ELKp79Y94gHmiSnpTafqDYZ5/+6u3XWfv9w/GLZ7dkAwPGZHJeL32K+tI7a+mgza5nC25aAbyfXF1N/nM+u9a3KUGyfkT7ZvoZDZyCSLAfk3flyHAKvPz1j++38+e6N6cX4VDs87SvP3vaN7Ls33i6+l5Kzp0PWeAb25nN2FbPqAogr1UkSoi6piOOhJk899NiPiRxOnva5cVbnMUGYrYcuHjG0k7O1U6tf3FKwYXb9rJPOwZAEYvl89n1199PL7teHn60TRsINoVjgalJsPP4l5NSZtbObzEWJi2PXqqg4+zBSNb1pcxsp57rTdPoxxBivaUj1U2g6zMpj1SGbGPj062Nt/RZB5GlLgIJ6IbJwxGig0y1U8XSDDbKDWIkeQrRBubyMYWbSGmSKmrn13yDzkDKrCs7S3RN0+S0CSOHV4dmC50tZapHyKXuvDn7c3bbtrPJp//9HPw+USmpSTltSu33pnfvOAmzwzv/w3wIMjH34tOnldtFX3oz1kSErfhsco205HqErm+nr99PrnALU9DPHo7HXUjjXEbwx+RdOTLYyTGQEB4RNXBzTD2pOMATRupfKOqkLV5p9E2/N3L5cvpep7wRGRMjmhEWOA1gGhDjGDsp9rkaqyuAi9N/hbqyaRpEV31GJ0pCOBMKxuBh4Mg9JQzLH79Mb6iXK4SdiDEyPqvgXdjPhZG1cyTDyWKZX2dQaTYSArk7EAWnUD4aQSbvqvamabJHVJ1vEQ7G9MnEm0YghZ2Ao0cOgb9cVv2QOd8CloWI1nz/nvyF01qVZshN0/SNpF9qPCnxbrfn3n27xP7Mapi82lnKzNNVl7ZzIFQ42uCZyZdvyDQcyr80iZ14JcMgBiAwAzxpXtmJqMFgjuxL7+y7kmGbm2a7vPqAqEa0b6afcdB+O32NV4wff/uo2w61oFGa1BfivGD78jMsrmXVeC8V+3gGC7nGc3pnLTizP/72cXp9dnJ0xDMY/R+OX7AHWhRgyAFu4xiDVGHaBG5IwiAtmKdvfDVtKJOOB+HlzsDB2sgTcTdY0rex5OnjY0QzspAMthwz3k3qeoLG6gpgre8y1XMee8iA+RdRy1osIxtJh4muge+2xbvXiVwDcwPkfOJKuzo7u0xf0IYcqs6277pUsObVoxCt+XpY+guzSztLmaX2D8cvskdUquQVDgf0ycx2QhE25c7me/uaKgoy8+AQ6ACZtqGhksMtDmi7C6Vb2I/CFJU2hjzGlzLL6eVpDX9hJP5+O30dKDGHS/LBC27TlAnfS5O0VsiOKJCQdeo7iwZ0lopOjo5K4wfyM/sVsGAHCEsmjAG6eWe3spBOnYX3UlR1ZZMyGYwvPQqisCjyZlXKLCGrwNBmYcQnk/A7gG+nrzV5FqnVoWBwA41gWFiAuIu/7eJakCxH4h0D4AdF8KVzYS5hfCpiYlCHzvw3R3PMdMLJ09eNUdjQaCFVoCcrGmMnpT1lY3UFcPHp00CJHWxlgoIU13ODl8nLE7HkOCYJVD7XISuMdnlRL5eQnhndqLhlBO3cZeAg9IZ0CdsTRvJvEMhvyjvtiz/8fQ23XcgsteMb0+AR1YUUJBwO6JOZ7Vw+zc+LqvwNn4YgEOhuzcGGiPTtm5kJRur0UMNxys30s+4ydJMNBaIC2+7K5DG+lFlOPzk60n7aqXphzLrkf8LE5U+31CRk4M30s6oOHpFh+A5ODQuZjFtlZ6kIFQAPKriJvzk/VQIpLTaE+wV6TsUSHTsxnkdRn6JgkvIMlmePVCbfEOTFVcoseTKBF1G7T4B3EcQMhKvv1duvV93rkJzJOgs20C/NAb61pZ06uByJpY28YhSUIYoYvi4dk4pMGxqpAnM7RLNv+kDg2u6COvoOOcFrdIa/5ZjcOdLOIPzpP66uAPJDzLCVzL/wqi1kTIlsZRJwAcAG6Aqd2TysT/15S67RaDamz62VxYmPIV2CR0FvEIi1nX/6VMostetvpugRla7k2Scz28mXJXjRyuqEujglyATVYJsaxokQNTCSusrGXNHyx8nZPExRvWWGBPIYHzohqpxeGlaWtoESPwYafQJBiW7Op8/O+0zivpNf3SmQ4GbpOzoHFA2s1hDZ0jVy4N0MJIzBRxBgFcIKgHLKBk0KvnNw1s4pGKMAw+BSZujU6VRaNpDedFDHMBz40cbZ5C8kA6vGtrv4CAQbGCmig0xIUzsJvBzJf0rwY/IuEEBVpHvmyFSEJVka+vv+htCE6eFuJs8B6nu5eLMBWdrARCrazM2sfes9oyoAeMis4ptJlHjBJqYO4HIW+zE+c2TeB4H6EUCxG6Le5LleGoO5XAZsLPL1/jsJmkoHtU6kImgPoVUj0Q5JyWM12KkpSJlscBbXcPCIelfy7JOZ7YTMLl6z8l94MpTzxrJO4rezUARKqMTpcgjxwEj6lRsgBplzgUsDwkgFolM4TMnrGs4yy+mUExqqF7fWJR8EKiVYwlQkWEzh8RAk4KMaVvo+0BkU5YfgoJF2hn79qMmD/pyKJTqmH8aPrADUpBzi0iMlP5zJix3yfiqWPJVA2cYsAM9ANMRvTi9ms9nt7JxAILAbs3jHqV6P2dY4viQP+d0qnoU3TPodSt/TDv3VVBSZa/z3MLSznD4QOIzndDRYXmfDaDMb5ZiyM+/eHbo13KTSx2uMrQBAirstEOd9gT6TqVaCfG5r5aIQqMAddmbPcdLgjRx/+Kpz8fQTeiAQnXqu89c3Oh6D4aPao2PyM1YwNa9ewoHM0k6e9Cu1q0cUqC8q1do+j8qND47AANqj3kEyXg6J6rsvVviN5m17eTa5hvbgPjr7Rqo6vDEWRXe/vsbOy1s8GtlDIEpDyTNRdQ3TKkY5Tw8W8qPKYWeO5gB5zkJD59KdAJN26nJTN1u5+JI2+M6wQi8dKX3P6R2GMeiQlv+OqQB4aMF8yOSBt7ICGDAphzh7pNm1MpN1sJIPkDOHgfTO0dQKAIlxOzsPlsNr3eV0FWs66RjayXe3eSSMRzGkz0v5u0KkN6KGvwNrMyRzX+YMRFNv9W0sOobrCJ30nTmfwxSmQ8KYTnVHx2t/VvdkPUMVwMZGcIluLGE3J4Z02YqRjyFzK4YNP1r9XUO8FXTPJUSPh+Vz2F1Z9lxWWe8BEnAq7kvQH6UC2Bfn17XzMU7rx5C5rl/leP67svKuO3eQQHiqLt9176DZNunvR8CpuC8x3dEKQF9AtXI975uTfFo/3M4s89lTB+8e9S3fs5tkA0YS0PfG/M5u5FwPM4EtEnAqbhHm44na0Qrg8Ry2ZBMwARMwARMwgaZpXAE4DUzABEzABEzgEAm4AjjEqNtnEzABEzABE3AF4BwwARMwARMwgUMk4ArgEKNun03ABEzABEzAFYBzwARMwARMwAQOkYArgEOMun02ARMwARMwAVcAzgETMAETMAETOEQCrgAOMer22QRMwARMwARcATgHTMAETMAETOAQCbgCOMSo22cTMAETMAETcAXgHDABEzABEzCBQyTgCuAQo26fTcAETMAETMAVgHPABEzABEzABA6RgCuAQ4y6fTYBEzABEzCB/wMGR8ITh3rLpAAAAABJRU5ErkJggg==" + }, + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "id": "a74fdc32", + "metadata": {}, + "source": [ + "1. Что делает команда git stash?\n", + " - Сохраняет изменения в рабочей копии в локальное хранилище\n", + "2. Как просмотреть список всех сохранённых изменений (стэшей)?\n", + " - Команда git stash list показывает сохраненные стеши\n", + "3. Какая команда применяется для использования верхнего стэша?\n", + " - Команда git stash apply\n", + "4. Как применить конкретный стэш по его номеру?\n", + " - git stash apply stash@N, где N - номер стеша\n", + "5. Чем отличается команда git stash apply от git stash pop?\n", + " - git stash apply применяет изменения из стэша, но оставляет его в списке\n", + " - git stash pop применяет изменения и удаляет этот стэш из списка\n", + "6. Что делает команда git stash drop?\n", + " - git stash drop stash@N - удалит стеш с номером N\n", + "7. Как полностью очистить все сохранённые стэши?\n", + " - git stash clear\n", + "8. В каких случаях удобно использовать git stash?\n", + " - Когда необходимо переключиться на другую ветку или задачу без необходимости закоммитить текущие изменения\n", + "9. Что произойдет, если выполнить git stash pop, но в проекте есть конфликтующие изменения?\n", + " - Будет сообщение о конфликте, требующем ручного разрешения\n", + "10. Можно ли восстановить удалённый стэш после выполнения git stash drop?\n", + " - Можно, если не закрыли терминал:\n", + " 1. git fsck --no-reflog | awk '/dangling commit/ {print $3}' - выведет список хешей коммитов, на которые больше нет ссылок\n", + " 2. git show <хеш_коммита> - посмотреть содержимое стеша\n", + " 3. git stash apply <хеш_коммита> - применить изменения как стэш\n", + "11. Что делает команда git stash save \"NAME_STASH\"\n", + " - Сохраняет изменения в стэш с комментариями, указанными в кавычках\n", + "12. Что делает команда git stash apply \"NUMBER_STASH\"\n", + " - Применяет конкретный стэш по номеру\n", + "13. Что делает команда git stash pop \"NUMBER_STASH\"\n", + "\n", + "Сохраните текущие изменения в стэш под названием \"SENATOROV ver1\", вставьте скриншот из терминала\n", + "\n", + "![image.png](attachment:image.png)\n", + "\n", + "Внесите любые изменения в ваш репозиторий и сохраните второй стэш под именем \"SENATOROV ver2\"\n", + "\n", + "![image-2.png](attachment:image-2.png)\n", + "\n", + "Восстановите ваш стэш \"SENATOROV ver1\", вставьте скриншот из терминала\n", + "\n", + "![image-3.png](attachment:image-3.png)\n", + "\n", + "Удалите все стеши из истории, вставьте скриншот из терминала\n", + "\n", + "![image-4.png](attachment:image-4.png)\n" + ] + }, + { + "cell_type": "markdown", + "id": "d642e2fd", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/git/stash.py b/git/stash.py new file mode 100644 index 00000000..c8c3012a --- /dev/null +++ b/git/stash.py @@ -0,0 +1,38 @@ +"""[TASK] STASH #3.""" + +# 1. Что делает команда git stash? +# - Сохраняет изменения в рабочей директории и индексированные изменения в локальное хранилище +# 2. Как просмотреть список всех сохранённых изменений (стэшей)? +# - Команда git stash list показывает сохраненные стеши +# 3. Какая команда применяется для использования верхнего стэша? +# - Команда git stash apply +# 4. Как применить конкретный стэш по его номеру? +# - git stash apply stash@{N}, где N - номер стеша +# 5. Чем отличается команда git stash apply от git stash pop? +# - git stash apply применяет изменения из стэша, но оставляет его в списке +# - git stash pop применяет изменения и удаляет этот стэш из списка +# 6. Что делает команда git stash drop? +# - git stash drop stash@{N} - удалит стеш с номером N +# 7. Как полностью очистить все сохранённые стэши? +# - git stash clear +# 8. В каких случаях удобно использовать git stash? +# - Когда необходимо переключиться на другую ветку или задачу без необходимости закоммитить текущие изменения +# 9. Что произойдет, если выполнить git stash pop, но в проекте есть конфликтующие изменения? +# - Будет сообщение о конфликте, требующем ручного разрешения +# 10. Можно ли восстановить удалённый стэш после выполнения git stash drop? +# - Можно, если не закрыли терминал (пока объекты не были удалены сборщиком мусора): +# 1. git fsck --no-reflog | awk '/dangling commit/ {print $3}' - выведет список хешей коммитов, на которые больше нет ссылок +# 2. git show <хеш_коммита> - посмотреть содержимое стеша +# 3. git stash apply <хеш_коммита> - применить изменения как стэш +# 11. Что делает команда git stash save "NAME_STASH" +# - Сохраняет изменения в стэш с комментариями, указанными в кавычках +# 12. Что делает команда git stash apply "NUMBER_STASH" +# - Применяет конкретный стэш по номеру +# 13. Что делает команда git stash pop "NUMBER_STASH" +# +# Сохраните текущие изменения в стэш под названием "SENATOROV ver1", вставьте скриншот из терминала +# +# Внесите любые изменения в ваш репозиторий и сохраните второй стэш под именем "SENATOROV ver2" +# Восстановите ваш стэш "SENATOROV ver1", вставьте скриншот из терминала +# Удалите все стеши из истории, вставьте скриншот из терминала +# From 5621f5b7314803adfda955fc78b948b19c142964 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sat, 1 Nov 2025 14:34:14 +0300 Subject: [PATCH 13/37] [TASK] issues #2 (https://github.com/SENATOROVAI/intro-cs/issues/2) Closes https://github.com/SENATOROVAI/intro-cs/issues/2 --- python/issues.ipynb | 150 ++++++++++++++++++++++++++++++++++++++++++++ python/issues.py | 118 ++++++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 python/issues.ipynb create mode 100644 python/issues.py diff --git a/python/issues.ipynb b/python/issues.ipynb new file mode 100644 index 00000000..c2ce1fb1 --- /dev/null +++ b/python/issues.ipynb @@ -0,0 +1,150 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c503200d", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"[TASK] issues #2.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "b9239e42", + "metadata": {}, + "source": [ + "### Общие вопросы\n", + "1. Что такое Issues на GitHub и для чего они используются?\n", + " - Это инструмент для отслеживания задач, багов, который используется для планирования и управления работой в репозиториях GitHub. \n", + "2. Чем Issues отличаются от других инструментов управления задачами?\n", + " - Это простой и гибкий трекер задач, интегрированный с самим кодом, пул-реквестами, коммитами и проектами. В отличие от корпоративных систем (например, Jira), Issues не имеют избыточных настроек, категорий и атрибутов, а их кастомизация достигается с помощью меток, майлстонов, связанных задач и автоматизации через GitHub.\n", + "3. Какие основные компоненты (поля) есть у каждого Issue?\n", + " - Title (Заголовок)\n", + " - Description (Описание)\n", + " - Labels (Метки)\n", + " - Assignees (Ответственные)\n", + " - Milestone (Веха)\n", + " - Projects (Связанные проекты)\n", + " - Comments (Комментарии)\n", + " - Issue type (Тип, если настроено в организации)\n", + " - State (Open/Closed — открыт/закрыт)\n", + " - Linked issues / Pull requests (Связанные задачи или PR)\n", + "\n", + "### Создание Issues\n", + "\n", + "1. Как создать новое Issue в репозитории?\n", + " 1) Заходим в файл в репозитории \n", + " 2) ЛКМ+shift-copy permalink\n", + " 3) Issues - New issue - Выбираем тип Issues\n", + " 4) Заполняем title и описание Issues\n", + " 5) Issues - set milestone\n", + " 6) Submit new issue\n", + "2. Какие данные рекомендуется указывать в описании Issue для лучшего понимания задачи?\n", + " - Указать ссылку на файл кода для issues\n", + " - Описание и название файла кода\n", + " - Описание проблемы или задачи\n", + " - Шаги для воспроизведения (если это баг)\n", + " - Ожидаемое и фактическое поведение\n", + " - Скриншоты или логи при необходимости\n", + " - Возможные решения или идеи\n", + " - Ссылки на связанные задачи или обсуждения\n", + "3. Какие теги (labels) можно добавить к Issue? Какие из них стандартные?\n", + " - Можно добавить стандартные и кастомные\n", + " - Стандартные: bug, enhancement, documentation, duplicate, good first issue, help wanted, invalid, question, wontfix\n", + "4. Как прикрепить Assignees (ответственных) к Issue?\n", + " - Assignees - выбрать пользователя или группу\n", + "\n", + "### Работа с Issues\n", + "\n", + "1. Как использовать Labels для классификации задач?\n", + " - Выставить теги в Labels в окне Issues и поставить нужные теги\n", + "2. Для чего нужен Milestone, и как связать его с Issue?\n", + " - Для того, чтобы привязать Issue к дате или определенному этапу\n", + " - Выставить дату или этап в Milestone в окне Issues\n", + "3. Как привязать Issue к пул-реквесту (Pull Request)?\n", + " - При создании pull request или коммита указать номер Issue в #123, либо использовать ключевые слова (\"Closes #123\", \"Fixes #123\")\n", + " - Можно вручную добавить связь через панель Linked issues\n", + "4. Как добавить комментарий к существующему Issue?\n", + " - В конце страницы Issue в поле комментария — написать текст и нажать \"Comment\", чтобы добавить сообщение\n", + "\n", + "### Закрытие и завершение Issues\n", + "\n", + "1. Как закрыть Issue вручную?\n", + " 1) Заходим в Issues\n", + " 2) Находим Файл\n", + " 3) Ветка - main\n", + " 4) Редактируем файл\n", + " 5) Commit changes\n", + " 6) Commit message - тайтл Issues, Extended description - closes (Номер ошибки)\n", + " 7) Sing off and commit changes\n", + "2. Можно ли автоматически закрыть Issue с помощью сообщения в коммите или пул-реквесте? Как это сделать?\n", + " - Если в сообщении коммита или pull request использовать ключевые слова: \"Closes #номер\", \"Fixes #номер\"\n", + "3. Как повторно открыть закрытое Issue, если работа ещё не завершена?\n", + " - Вкладка Issue - Выбрать Issue - Нажать \"Reopen issue\"\n", + "\n", + "### Фильтрация и поиск\n", + "\n", + "1. Как найти все открытые или закрытые Issues в репозитории?\n", + " - Во вкладке Issues по нажать \"Closed\" или в поиске, напишите is:open или is:closed для фильтрации.\n", + "2. Как использовать фильтры для поиска Issues по меткам, исполнителям или другим критериям?\n", + " - В поиске написать, например: label:bug assignee:username milestone:\"1.0.0\", либо использовать фильтры сверху (Labels, Assignee, Milestones, Projects).\n", + "3. Как сортировать Issues по приоритету, дате создания или другим параметрам?\n", + " - В меню (\"Sort\") на вкладке Issues: доступны опции по дате создания, обновления, количеству комментариев и т.д. Приоритет обычно выставляется ярлыками (\"priority: high\")\n", + "\n", + "### Интеграции и автоматизация\n", + "\n", + "1. Как настроить автоматические уведомления о новых или изменённых Issues?\n", + " - В репозитории нажать \"Watch\" — выбрать интересующий вариант уведомлений\n", + "2. Что такое Projects в контексте GitHub, и как связать их с Issues?\n", + " - Projects - это доски для визуального управления задачами. Issues можно добавлять в проекты для отслеживания статуса и автоматизации рабочих процессов.\n", + "3. Какие сторонние инструменты можно использовать для автоматизации работы с Issues (например, боты, Webhooks)?\n", + " - GitHub Actions (боты и скрипты для автоматизации работ)\n", + " - Webhooks для интеграции с внешними сервисами\n", + " - Сторонние боты типа probot, интеграция с Jira, Trello\n", + "\n", + "### Коллаборация\n", + "\n", + "1. Как упомянуть другого пользователя в комментарии к Issue?\n", + " - Использовать @username\n", + "2. Как запросить дополнительные данные или уточнения у автора Issue?\n", + " - Использовать упоминание в комментарии. Можно дополнительно добавить label \"needs info\"\n", + "3. Что делать, если Issue неактуально или его нужно объединить с другим?\n", + " - Для устаревших задач — закройте Issue с пометкой: \"Invalid\", \"Outdated\", кратко описав причину\n", + "Для объединения — оставьте ссылку на дубликат (\"Duplicate of #номер\"), закройте и используйте ярлык \"duplicate\"\n", + "\n", + "### Практические аспекты\n", + "\n", + "1. Как использовать шаблоны для создания Issues?\n", + " - В разделе Settings репозитория можно добавить шаблоны Issue (Issue Templates) для разных типов задач\n", + "2. Что такое Linked Issues, и как создать связь между задачами?\n", + " - Linked Issues - это задачи, логически связанные между собой (например, подзадачи). Связать можно с помощью панели \"Linked issues\", а также упоминания через #номер в тексте\n", + "3. Какие метрики (например, время выполнения) можно отслеживать с помощью Issues?\n", + " - Количество открытых/закрытых задач\n", + " - Время выполнения задачи\n", + " - Активность по assigned/closed issues\n", + " - Число комментариев\n", + "4. Какие best practices рекомендуются при работе с Issues в команде?\n", + " - Используйте шаблоны и метки для стандартизации.\n", + " - Назначайте ответственных\n", + " - Описывайте задачи понятно, структурированно и без двусмысленности\n", + " - Переводите устаревшие/дублирующие Issues в архив\n", + " - Интегрируйте Issues в pipeline разработки через Pull Request’ы, Projects и автоматизацию" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/issues.py b/python/issues.py new file mode 100644 index 00000000..d56b9d01 --- /dev/null +++ b/python/issues.py @@ -0,0 +1,118 @@ +"""[TASK] issues #2.""" + +# ### Общие вопросы +# 1. Что такое Issues на GitHub и для чего они используются? +# - Это инструмент для отслеживания задач, багов, который используется для планирования и управления работой в репозиториях GitHub. +# 2. Чем Issues отличаются от других инструментов управления задачами? +# - Это простой и гибкий трекер задач, интегрированный с самим кодом, пул-реквестами, коммитами и проектами. В отличие от корпоративных систем (например, Jira), Issues не имеют избыточных настроек, категорий и атрибутов, а их кастомизация достигается с помощью меток, майлстонов, связанных задач и автоматизации через GitHub. +# 3. Какие основные компоненты (поля) есть у каждого Issue? +# - Title (Заголовок) +# - Description (Описание) +# - Labels (Метки) +# - Assignees (Ответственные) +# - Milestone (Веха) +# - Projects (Связанные проекты) +# - Comments (Комментарии) +# - Issue type (Тип, если настроено в организации) +# - State (Open/Closed — открыт/закрыт) +# - Linked issues / Pull requests (Связанные задачи или PR) +# +# ### Создание Issues +# +# 1. Как создать новое Issue в репозитории? +# 1) Заходим в файл в репозитории +# 2) ЛКМ+shift-copy permalink +# 3) Issues - New issue - Выбираем тип Issues +# 4) Заполняем title и описание Issues +# 5) Issues - set milestone +# 6) Submit new issue +# 2. Какие данные рекомендуется указывать в описании Issue для лучшего понимания задачи? +# - Указать ссылку на файл кода для issues +# - Описание и название файла кода +# - Описание проблемы или задачи +# - Шаги для воспроизведения (если это баг) +# - Ожидаемое и фактическое поведение +# - Скриншоты или логи при необходимости +# - Возможные решения или идеи +# - Ссылки на связанные задачи или обсуждения +# 3. Какие теги (labels) можно добавить к Issue? Какие из них стандартные? +# - Можно добавить стандартные и кастомные +# - Стандартные: bug, enhancement, documentation, duplicate, good first issue, help wanted, invalid, question, wontfix +# 4. Как прикрепить Assignees (ответственных) к Issue? +# - Assignees - выбрать пользователя или группу +# +# ### Работа с Issues +# +# 1. Как использовать Labels для классификации задач? +# - Выставить теги в Labels в окне Issues и поставить нужные теги +# 2. Для чего нужен Milestone, и как связать его с Issue? +# - Для того, чтобы привязать Issue к дате или определенному этапу +# - Выставить дату или этап в Milestone в окне Issues +# 3. Как привязать Issue к пул-реквесту (Pull Request)? +# - При создании pull request или коммита указать номер Issue в #123, либо использовать ключевые слова ("Closes #123", "Fixes #123") +# - Можно вручную добавить связь через панель Linked issues +# 4. Как добавить комментарий к существующему Issue? +# - В конце страницы Issue в поле комментария — написать текст и нажать "Comment", чтобы добавить сообщение +# +# ### Закрытие и завершение Issues +# +# 1. Как закрыть Issue вручную? +# 1) Заходим в Issues +# 2) Находим Файл +# 3) Ветка - main +# 4) Редактируем файл +# 5) Commit changes +# 6) Commit message - тайтл Issues, Extended description - closes (Номер ошибки) +# 7) Sing off and commit changes +# 2. Можно ли автоматически закрыть Issue с помощью сообщения в коммите или пул-реквесте? Как это сделать? +# - Если в сообщении коммита или pull request использовать ключевые слова: "Closes #номер", "Fixes #номер" +# 3. Как повторно открыть закрытое Issue, если работа ещё не завершена? +# - Вкладка Issue - Выбрать Issue - Нажать "Reopen issue" +# +# ### Фильтрация и поиск +# +# 1. Как найти все открытые или закрытые Issues в репозитории? +# - Во вкладке Issues по нажать "Closed" или в поиске, напишите is:open или is:closed для фильтрации. +# 2. Как использовать фильтры для поиска Issues по меткам, исполнителям или другим критериям? +# - В поиске написать, например: label:bug assignee:username milestone:"1.0.0", либо использовать фильтры сверху (Labels, Assignee, Milestones, Projects). +# 3. Как сортировать Issues по приоритету, дате создания или другим параметрам? +# - В меню ("Sort") на вкладке Issues: доступны опции по дате создания, обновления, количеству комментариев и т.д. Приоритет обычно выставляется ярлыками ("priority: high") +# +# ### Интеграции и автоматизация +# +# 1. Как настроить автоматические уведомления о новых или изменённых Issues? +# - В репозитории нажать "Watch" — выбрать интересующий вариант уведомлений +# 2. Что такое Projects в контексте GitHub, и как связать их с Issues? +# - Projects - это доски для визуального управления задачами. Issues можно добавлять в проекты для отслеживания статуса и автоматизации рабочих процессов. +# 3. Какие сторонние инструменты можно использовать для автоматизации работы с Issues (например, боты, Webhooks)? +# - GitHub Actions (боты и скрипты для автоматизации работ) +# - Webhooks для интеграции с внешними сервисами +# - Сторонние боты типа probot, интеграция с Jira, Trello +# +# ### Коллаборация +# +# 1. Как упомянуть другого пользователя в комментарии к Issue? +# - Использовать @username +# 2. Как запросить дополнительные данные или уточнения у автора Issue? +# - Использовать упоминание в комментарии. Можно дополнительно добавить label "needs info" +# 3. Что делать, если Issue неактуально или его нужно объединить с другим? +# - Для устаревших задач — закройте Issue с пометкой: "Invalid", "Outdated", кратко описав причину +# Для объединения — оставьте ссылку на дубликат ("Duplicate of #номер"), закройте и используйте ярлык "duplicate" +# +# ### Практические аспекты +# +# 1. Как использовать шаблоны для создания Issues? +# - В разделе Settings репозитория можно добавить шаблоны Issue (Issue Templates) для разных типов задач +# 2. Что такое Linked Issues, и как создать связь между задачами? +# - Linked Issues - это задачи, логически связанные между собой (например, подзадачи). Связать можно с помощью панели "Linked issues", а также упоминания через #номер в тексте +# 3. Какие метрики (например, время выполнения) можно отслеживать с помощью Issues? +# - Количество открытых/закрытых задач +# - Время выполнения задачи +# - Активность по assigned/closed issues +# - Число комментариев +# 4. Какие best practices рекомендуются при работе с Issues в команде? +# - Используйте шаблоны и метки для стандартизации. +# - Назначайте ответственных +# - Описывайте задачи понятно, структурированно и без двусмысленности +# - Переводите устаревшие/дублирующие Issues в архив +# - Интегрируйте Issues в pipeline разработки через Pull Request’ы, Projects и автоматизацию From af076f522bbf6f2fa41b67df99302a55a779bea6 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 3 Nov 2025 00:00:18 +0300 Subject: [PATCH 14/37] =?UTF-8?q?[TASK]=20=D0=9A=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D0=B1=D1=8C=D1=8E=D1=82=D0=B8=D0=BD=D0=B3=20=D0=B2=20Ope?= =?UTF-8?q?n=20Source=20=20#8=20(https://github.com/SENATOROVAI/intro-cs/i?= =?UTF-8?q?ssues/8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/SENATOROVAI/intro-cs/issues/8 --- github/quiz.ipynb | 195 ++++++++++++++++++++++++++++++++++++++++++++++ github/quiz.py | 165 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 github/quiz.ipynb create mode 100644 github/quiz.py diff --git a/github/quiz.ipynb b/github/quiz.ipynb new file mode 100644 index 00000000..e681c9fe --- /dev/null +++ b/github/quiz.ipynb @@ -0,0 +1,195 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "44f7f6e5", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"[TASK] Контрибьютинг в Open Source #8.\"\"\"" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "id": "ed8d0ce1", + "metadata": {}, + "source": [ + "GitHub\n", + "1.1. Что такое GitHub?\n", + " - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions.\n", + "\n", + "1.2. Как GitHub связан с Git?\n", + " Git — это распределенная система контроля версий \n", + " GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями.\n", + " - Основные различия:​\n", + " Git — программное обеспечение для локального управления версиями\n", + " GitHub — облачный сервис для хостинга Git-репозиториев с функциями совместной работы\n", + " Git работает через командную строку, GitHub предоставляет графический интерфейс\n", + " Git бесплатен и с открытым кодом, GitHub принадлежит Microsoft и имеет платные тарифы\n", + "\n", + "1.3. Чем отличается fork репозитория от его клонирования (clone)?\n", + " - Fork (форк):​\n", + " Создает полностью независимую копию репозитория на вашем GitHub-аккаунте\n", + " Используется для внесения вкладов в чужие проекты через pull requests\n", + " Оригинальные разработчики не имеют доступа к вашему форку\n", + " - Clone (клонирование):​\n", + " Создает локальную копию репозитория на вашем компьютере\n", + " Копия остается связанной с исходным репозиторием\n", + " Вы можете синхронизировать изменения через git push и git pull\n", + "\n", + "1.4. Зачем нужны и как работают pull requests?\n", + " - Pull request (PR) — это механизм для предложения изменений в проект.\n", + " - Назначение:​\n", + " Уведомить других разработчиков о завершенной функции\n", + " Инициировать code review\n", + " Обсудить предлагаемые изменения\n", + " Получить обратную связь перед слиянием кода\n", + " - Как работает:​\n", + " Разработчик создает ветку с изменениями\n", + " Отправляет ветку в репозиторий (свой fork или общий)\n", + " Открывает pull request через GitHub\n", + " Команда обсуждает и проверяет код\n", + " Вносятся дополнительные изменения при необходимости\n", + " Сопровождающий проекта принимает и сливает изменения\n", + "\n", + "1.5. GitHub использует ваш почтовый адрес для привязки ваших Git коммитов к вашей учётной записи?\n", + " Да\n", + "\n", + "1.6 Какая команда генерирует SSH ключ для Доступа по SSH к репозиторию (Рисунок 83)\n", + " - Команда для генерации SSH-ключа:​​\n", + " ssh-keygen -t rsa -b 4096 -C \"your_email@example.com\"\n", + " - Или с использованием более современного алгоритма:\n", + " ssh-keygen -t ed25519 -C \"your_email@example.com\"\n", + " Параметры:​\n", + " -t — тип ключа (rsa, ed25519)\n", + " -b 4096 — размер ключа в битах (для RSA)\n", + " -C — комментарий (обычно ваш email)\n", + "\n", + "Внесение собственного вклада в проекты\n", + "2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117)\n", + " Как открыть запрос слияния:\n", + " Создайте первую ветку (feature-a) из основной ветки (main/master).\n", + " Откройте Merge request A из feature-a в main.\n", + " Создайте вторую ветку (feature-b) из feature-a (а не из main).\n", + " Откройте Merge request B из feature-b в main\n", + " Зачем нужно:\n", + " Разделение сложных задач\n", + " Параллельная разработка\n", + " Управление порядком интеграции\n", + "\n", + "Рабочий процесс с использованием GitHub\n", + "3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект.\n", + " Форкнуть проект\n", + " Клонировать форк локально\n", + " Создать отдельную ветку для изменений\n", + " Внести изменения\n", + " Закоммитить изменения\n", + " Отправить изменения в свой форк\n", + " Создать Pull Request\n", + " Подождать проверки\n", + "\n", + "3.1 Какие практики принято соблюдать при создании Pull Request чтобы закрыть автоматический issues?\n", + " В описании pull request использовать ключевые слова и номер issue:\n", + " Fixes #123\n", + " Closes #456\n", + " Resolves #789\n", + "Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues?\n", + " тоже самое, что и в пулл реквесте\n", + "\n", + "3.2 Как отклонить/закрыть пул реквест? (предоставьте скриншот где это в гитхабе)\n", + " ![image.png](attachment:image.png)\n", + "\n", + "3.3 Перед отправкой пул реквеста нужно ли создавать ишьюс?\n", + " Нет, это не обязательно, но зависит от практик конкретного проекта.\n", + "3.4 В какой вкладке можно посмотреть список изменений который был в пул реквесте? (Рисунок 92)\n", + " Files changed\n", + "3.5 В какой вкладке находится страница обсуждений пул реквеста? (Рисунок 94)\n", + " Conversation\n", + "\n", + "Создание запроса на слияние\n", + "4 Можно ли открыть пул реквест, если вы ничего не вносили в FORK?\n", + " Нет\n", + "\n", + "4.1 Что нужно сделать чтобы открыть пул реквест? (Рисунок 90)\n", + " Сделать форк (копию) нужного репозитория на GitHub и склонировать его себе локально.\n", + " Создать новую ветку в локальном репозитории для ваших изменений.\n", + " Внести необходимые правки в код или файлы и закоммитить изменения в этой ветке.\n", + " Отправить (push) вашу ветку с изменениями в ваш форк на GitHub.\n", + " На сайте GitHub перейти в ваш форк, и после пуша появится кнопка \"Compare & pull request\" — нажать ее.\n", + " Убедиться, что выбраны правильные ветки — откуда (ваша ветка) и куда (обычно main или master основного репозитория).\n", + " Написать заголовок и описание к пул реквесту, чтобы объяснить суть изменений.\n", + " Создать сам пул реквест кнопкой \"Create pull request\".\n", + "\n", + "4.2 Что нужно сделать Если ваш Форк устарел?\n", + " Нужно синхронизировать его с оригинальным репозиторием:\n", + " Добавить исходный репозиторий как удалённый с именем «upstream»\n", + " Получить все изменения из оригинального репозитория\n", + " Переключиться на нужную ветку\n", + " Синхронизировать изменения\n", + " Отправить изменения в свой форк\n", + "\n", + "4.3 Что нужно сделать если в пул реквесте имеются конфликты слияния (Рисунок 96)\n", + " Добавляем исходный репозиторий как удалённый с именем «upstream»\n", + " Получаем последние изменения из него\n", + " Сливаем основную ветку в нашу тематическую\n", + " Исправляем указанный конфликт\n", + " Отправляем изменения в ту же тематическую ветку\n", + " \n", + "Отрывки кода\n", + "5 Что нужно сделать Для добавления отрывка кода в комментарии к ишьюсу? (Рисунок 104)\n", + " Для добавления отрывка кода следует обрамить его обратными кавычками\n", + "5.1 На какую клавишу нажать клавишу чтобы выделенный текст был включён как цитата в ваш комментарий?(Рисунок 105)\n", + " Клавиша \"r\"\n", + "5.2 Как вставить картинку в ишьюс? (Рисунок 108)\n", + " Перетащить картинку или скопировать изображение\n", + "\n", + "Поддержание GitHub репозитория в актуальном состоянии\n", + "6 Как понять что ваш форк устарел?\n", + " При появлении в оригинальном репозитории новых коммитов GitHub информирует вас при помощи сообщения: This branch is 5 commits behind progit:master\n", + "\n", + "6.1 Как обновить форк?\n", + " Sync fork - Update branch\n", + "\n", + "Добавление участников\n", + "7 Как добавить участников в ваш репозиторий, чтобы команда могла работать над одним репозиторием? (Рисунок 112)\n", + " «Settings» - «Collaborators» - «Add collaborator»\n", + "\n", + "Упоминания и уведомления\n", + "8 Какой символ нужен для упоминания кого-либо? (Рисунок 118)\n", + " Символ \"@\"\n", + "8.1 Где находится Центр уведомлений, напишите ссылку (Рисунок 121)\n", + " https://github.com/notifications\n", + "\n", + "Особенные файлы\n", + "9 Что такое и зачем нужен файл README\n", + " Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику.\n", + "\n", + "9.1 Что такое и зачем нужен файл CONTRIBUTING (Рисунок 122)\n", + " Файл CONTRIBUTING — это документ в репозитории, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта. Основная цель этого файла — помочь новым участникам понять, как правильно взаимодействовать с проектом и что нужно делать для успешного добавления изменений.\n", + " Он описывает процесс участия: как клонировать репозиторий, создавать ветки, вносить изменения и отправлять их на проверку через pull requests. В файле устанавливаются стандарты и требования по коду, формату коммитов, стилю программирования, тестированию и другим аспектам разработки.\n", + "\n", + "Управление проектом\n", + "10 Как изменить основную ветку (Рисунок 123)\n", + " «Options» - Default branch\n", + "10.1 Как передать проект? какая кнопка? (рисунок 124)\n", + " «Options» - «Transfer ownership» \n", + "10.2 Что такое файл .gitignore?\n", + " Файл .gitignore — это текстовый файл в системе контроля версий Git, который содержит список файлов и каталогов, которые Git должен игнорировать и не отслеживать.\n", + "\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/github/quiz.py b/github/quiz.py new file mode 100644 index 00000000..318556dd --- /dev/null +++ b/github/quiz.py @@ -0,0 +1,165 @@ +"""[TASK] Контрибьютинг в Open Source #8.""" + +# GitHub +# 1.1. Что такое GitHub? +# - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions. +# +# 1.2. Как GitHub связан с Git? +# Git — это распределенная система контроля версий +# GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями. +# - Основные различия:​ +# Git — программное обеспечение для локального управления версиями +# GitHub — облачный сервис для хостинга Git-репозиториев с функциями совместной работы +# Git работает через командную строку, GitHub предоставляет графический интерфейс +# Git бесплатен и с открытым кодом, GitHub принадлежит Microsoft и имеет платные тарифы +# +# 1.3. Чем отличается fork репозитория от его клонирования (clone)? +# - Fork (форк):​ +# Создает полностью независимую копию репозитория на вашем GitHub-аккаунте +# Используется для внесения вкладов в чужие проекты через pull requests +# Оригинальные разработчики не имеют доступа к вашему форку +# - Clone (клонирование):​ +# Создает локальную копию репозитория на вашем компьютере +# Копия остается связанной с исходным репозиторием +# Вы можете синхронизировать изменения через git push и git pull +# +# 1.4. Зачем нужны и как работают pull requests? +# - Pull request (PR) — это механизм для предложения изменений в проект. +# - Назначение:​ +# Уведомить других разработчиков о завершенной функции +# Инициировать code review +# Обсудить предлагаемые изменения +# Получить обратную связь перед слиянием кода +# - Как работает:​ +# Разработчик создает ветку с изменениями +# Отправляет ветку в репозиторий (свой fork или общий) +# Открывает pull request через GitHub +# Команда обсуждает и проверяет код +# Вносятся дополнительные изменения при необходимости +# Сопровождающий проекта принимает и сливает изменения +# +# 1.5. GitHub использует ваш почтовый адрес для привязки ваших Git коммитов к вашей учётной записи? +# Да +# +# 1.6 Какая команда генерирует SSH ключ для Доступа по SSH к репозиторию (Рисунок 83) +# - Команда для генерации SSH-ключа:​​ +# ssh-keygen -t rsa -b 4096 -C "your_email@example.com" +# - Или с использованием более современного алгоритма: +# ssh-keygen -t ed25519 -C "your_email@example.com" +# Параметры:​ +# -t — тип ключа (rsa, ed25519) +# -b 4096 — размер ключа в битах (для RSA) +# -C — комментарий (обычно ваш email) +# +# Внесение собственного вклада в проекты +# 2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117) +# Как открыть запрос слияния: +# Создайте первую ветку (feature-a) из основной ветки (main/master). +# Откройте Merge request A из feature-a в main. +# Создайте вторую ветку (feature-b) из feature-a (а не из main). +# Откройте Merge request B из feature-b в main +# Зачем нужно: +# Разделение сложных задач +# Параллельная разработка +# Управление порядком интеграции +# +# Рабочий процесс с использованием GitHub +# 3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект. +# Форкнуть проект +# Клонировать форк локально +# Создать отдельную ветку для изменений +# Внести изменения +# Закоммитить изменения +# Отправить изменения в свой форк +# Создать Pull Request +# Подождать проверки +# +# 3.1 Какие практики принято соблюдать при создании Pull Request чтобы закрыть автоматический issues? +# В описании pull request использовать ключевые слова и номер issue: +# Fixes #123 +# Closes #456 +# Resolves #789 +# Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues? +# тоже самое, что и в пулл реквесте +# +# 3.2 Как отклонить/закрыть пул реквест? (предоставьте скриншот где это в гитхабе) +# ![image.png](attachment:image.png) +# +# 3.3 Перед отправкой пул реквеста нужно ли создавать ишьюс? +# Нет, это не обязательно, но зависит от практик конкретного проекта. +# 3.4 В какой вкладке можно посмотреть список изменений который был в пул реквесте? (Рисунок 92) +# Files changed +# 3.5 В какой вкладке находится страница обсуждений пул реквеста? (Рисунок 94) +# Conversation +# +# Создание запроса на слияние +# 4 Можно ли открыть пул реквест, если вы ничего не вносили в FORK? +# Нет +# +# 4.1 Что нужно сделать чтобы открыть пул реквест? (Рисунок 90) +# Сделать форк (копию) нужного репозитория на GitHub и склонировать его себе локально. +# Создать новую ветку в локальном репозитории для ваших изменений. +# Внести необходимые правки в код или файлы и закоммитить изменения в этой ветке. +# Отправить (push) вашу ветку с изменениями в ваш форк на GitHub. +# На сайте GitHub перейти в ваш форк, и после пуша появится кнопка "Compare & pull request" — нажать ее. +# Убедиться, что выбраны правильные ветки — откуда (ваша ветка) и куда (обычно main или master основного репозитория). +# Написать заголовок и описание к пул реквесту, чтобы объяснить суть изменений. +# Создать сам пул реквест кнопкой "Create pull request". +# +# 4.2 Что нужно сделать Если ваш Форк устарел? +# Нужно синхронизировать его с оригинальным репозиторием: +# Добавить исходный репозиторий как удалённый с именем «upstream» +# Получить все изменения из оригинального репозитория +# Переключиться на нужную ветку +# Синхронизировать изменения +# Отправить изменения в свой форк +# +# 4.3 Что нужно сделать если в пул реквесте имеются конфликты слияния (Рисунок 96) +# Добавляем исходный репозиторий как удалённый с именем «upstream» +# Получаем последние изменения из него +# Сливаем основную ветку в нашу тематическую +# Исправляем указанный конфликт +# Отправляем изменения в ту же тематическую ветку +# +# Отрывки кода +# 5 Что нужно сделать Для добавления отрывка кода в комментарии к ишьюсу? (Рисунок 104) +# Для добавления отрывка кода следует обрамить его обратными кавычками +# 5.1 На какую клавишу нажать клавишу чтобы выделенный текст был включён как цитата в ваш комментарий?(Рисунок 105) +# Клавиша "r" +# 5.2 Как вставить картинку в ишьюс? (Рисунок 108) +# Перетащить картинку или скопировать изображение +# +# Поддержание GitHub репозитория в актуальном состоянии +# 6 Как понять что ваш форк устарел? +# При появлении в оригинальном репозитории новых коммитов GitHub информирует вас при помощи сообщения: This branch is 5 commits behind progit:master +# +# 6.1 Как обновить форк? +# Sync fork - Update branch +# +# Добавление участников +# 7 Как добавить участников в ваш репозиторий, чтобы команда могла работать над одним репозиторием? (Рисунок 112) +# «Settings» - «Collaborators» - «Add collaborator» +# +# Упоминания и уведомления +# 8 Какой символ нужен для упоминания кого-либо? (Рисунок 118) +# Символ "@" +# 8.1 Где находится Центр уведомлений, напишите ссылку (Рисунок 121) +# https://github.com/notifications +# +# Особенные файлы +# 9 Что такое и зачем нужен файл README +# Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику. +# +# 9.1 Что такое и зачем нужен файл CONTRIBUTING (Рисунок 122) +# Файл CONTRIBUTING — это документ в репозитории, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта. Основная цель этого файла — помочь новым участникам понять, как правильно взаимодействовать с проектом и что нужно делать для успешного добавления изменений. +# Он описывает процесс участия: как клонировать репозиторий, создавать ветки, вносить изменения и отправлять их на проверку через pull requests. В файле устанавливаются стандарты и требования по коду, формату коммитов, стилю программирования, тестированию и другим аспектам разработки. +# +# Управление проектом +# 10 Как изменить основную ветку (Рисунок 123) +# «Options» - Default branch +# 10.1 Как передать проект? какая кнопка? (рисунок 124) +# «Options» - «Transfer ownership» +# 10.2 Что такое файл .gitignore? +# Файл .gitignore — это текстовый файл в системе контроля версий Git, который содержит список файлов и каталогов, которые Git должен игнорировать и не отслеживать. +# +# From 8b6da620289d6e1f7e46737fafce360b0825538f Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Mon, 10 Nov 2025 21:06:25 +0300 Subject: [PATCH 15/37] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20is?= =?UTF-8?q?sues.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/issues.py | 118 ----------------------------------------------- 1 file changed, 118 deletions(-) delete mode 100644 python/issues.py diff --git a/python/issues.py b/python/issues.py deleted file mode 100644 index d56b9d01..00000000 --- a/python/issues.py +++ /dev/null @@ -1,118 +0,0 @@ -"""[TASK] issues #2.""" - -# ### Общие вопросы -# 1. Что такое Issues на GitHub и для чего они используются? -# - Это инструмент для отслеживания задач, багов, который используется для планирования и управления работой в репозиториях GitHub. -# 2. Чем Issues отличаются от других инструментов управления задачами? -# - Это простой и гибкий трекер задач, интегрированный с самим кодом, пул-реквестами, коммитами и проектами. В отличие от корпоративных систем (например, Jira), Issues не имеют избыточных настроек, категорий и атрибутов, а их кастомизация достигается с помощью меток, майлстонов, связанных задач и автоматизации через GitHub. -# 3. Какие основные компоненты (поля) есть у каждого Issue? -# - Title (Заголовок) -# - Description (Описание) -# - Labels (Метки) -# - Assignees (Ответственные) -# - Milestone (Веха) -# - Projects (Связанные проекты) -# - Comments (Комментарии) -# - Issue type (Тип, если настроено в организации) -# - State (Open/Closed — открыт/закрыт) -# - Linked issues / Pull requests (Связанные задачи или PR) -# -# ### Создание Issues -# -# 1. Как создать новое Issue в репозитории? -# 1) Заходим в файл в репозитории -# 2) ЛКМ+shift-copy permalink -# 3) Issues - New issue - Выбираем тип Issues -# 4) Заполняем title и описание Issues -# 5) Issues - set milestone -# 6) Submit new issue -# 2. Какие данные рекомендуется указывать в описании Issue для лучшего понимания задачи? -# - Указать ссылку на файл кода для issues -# - Описание и название файла кода -# - Описание проблемы или задачи -# - Шаги для воспроизведения (если это баг) -# - Ожидаемое и фактическое поведение -# - Скриншоты или логи при необходимости -# - Возможные решения или идеи -# - Ссылки на связанные задачи или обсуждения -# 3. Какие теги (labels) можно добавить к Issue? Какие из них стандартные? -# - Можно добавить стандартные и кастомные -# - Стандартные: bug, enhancement, documentation, duplicate, good first issue, help wanted, invalid, question, wontfix -# 4. Как прикрепить Assignees (ответственных) к Issue? -# - Assignees - выбрать пользователя или группу -# -# ### Работа с Issues -# -# 1. Как использовать Labels для классификации задач? -# - Выставить теги в Labels в окне Issues и поставить нужные теги -# 2. Для чего нужен Milestone, и как связать его с Issue? -# - Для того, чтобы привязать Issue к дате или определенному этапу -# - Выставить дату или этап в Milestone в окне Issues -# 3. Как привязать Issue к пул-реквесту (Pull Request)? -# - При создании pull request или коммита указать номер Issue в #123, либо использовать ключевые слова ("Closes #123", "Fixes #123") -# - Можно вручную добавить связь через панель Linked issues -# 4. Как добавить комментарий к существующему Issue? -# - В конце страницы Issue в поле комментария — написать текст и нажать "Comment", чтобы добавить сообщение -# -# ### Закрытие и завершение Issues -# -# 1. Как закрыть Issue вручную? -# 1) Заходим в Issues -# 2) Находим Файл -# 3) Ветка - main -# 4) Редактируем файл -# 5) Commit changes -# 6) Commit message - тайтл Issues, Extended description - closes (Номер ошибки) -# 7) Sing off and commit changes -# 2. Можно ли автоматически закрыть Issue с помощью сообщения в коммите или пул-реквесте? Как это сделать? -# - Если в сообщении коммита или pull request использовать ключевые слова: "Closes #номер", "Fixes #номер" -# 3. Как повторно открыть закрытое Issue, если работа ещё не завершена? -# - Вкладка Issue - Выбрать Issue - Нажать "Reopen issue" -# -# ### Фильтрация и поиск -# -# 1. Как найти все открытые или закрытые Issues в репозитории? -# - Во вкладке Issues по нажать "Closed" или в поиске, напишите is:open или is:closed для фильтрации. -# 2. Как использовать фильтры для поиска Issues по меткам, исполнителям или другим критериям? -# - В поиске написать, например: label:bug assignee:username milestone:"1.0.0", либо использовать фильтры сверху (Labels, Assignee, Milestones, Projects). -# 3. Как сортировать Issues по приоритету, дате создания или другим параметрам? -# - В меню ("Sort") на вкладке Issues: доступны опции по дате создания, обновления, количеству комментариев и т.д. Приоритет обычно выставляется ярлыками ("priority: high") -# -# ### Интеграции и автоматизация -# -# 1. Как настроить автоматические уведомления о новых или изменённых Issues? -# - В репозитории нажать "Watch" — выбрать интересующий вариант уведомлений -# 2. Что такое Projects в контексте GitHub, и как связать их с Issues? -# - Projects - это доски для визуального управления задачами. Issues можно добавлять в проекты для отслеживания статуса и автоматизации рабочих процессов. -# 3. Какие сторонние инструменты можно использовать для автоматизации работы с Issues (например, боты, Webhooks)? -# - GitHub Actions (боты и скрипты для автоматизации работ) -# - Webhooks для интеграции с внешними сервисами -# - Сторонние боты типа probot, интеграция с Jira, Trello -# -# ### Коллаборация -# -# 1. Как упомянуть другого пользователя в комментарии к Issue? -# - Использовать @username -# 2. Как запросить дополнительные данные или уточнения у автора Issue? -# - Использовать упоминание в комментарии. Можно дополнительно добавить label "needs info" -# 3. Что делать, если Issue неактуально или его нужно объединить с другим? -# - Для устаревших задач — закройте Issue с пометкой: "Invalid", "Outdated", кратко описав причину -# Для объединения — оставьте ссылку на дубликат ("Duplicate of #номер"), закройте и используйте ярлык "duplicate" -# -# ### Практические аспекты -# -# 1. Как использовать шаблоны для создания Issues? -# - В разделе Settings репозитория можно добавить шаблоны Issue (Issue Templates) для разных типов задач -# 2. Что такое Linked Issues, и как создать связь между задачами? -# - Linked Issues - это задачи, логически связанные между собой (например, подзадачи). Связать можно с помощью панели "Linked issues", а также упоминания через #номер в тексте -# 3. Какие метрики (например, время выполнения) можно отслеживать с помощью Issues? -# - Количество открытых/закрытых задач -# - Время выполнения задачи -# - Активность по assigned/closed issues -# - Число комментариев -# 4. Какие best practices рекомендуются при работе с Issues в команде? -# - Используйте шаблоны и метки для стандартизации. -# - Назначайте ответственных -# - Описывайте задачи понятно, структурированно и без двусмысленности -# - Переводите устаревшие/дублирующие Issues в архив -# - Интегрируйте Issues в pipeline разработки через Pull Request’ы, Projects и автоматизацию From a890c159217ee505c8d6a66e4b58fb8d22947fc5 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Thu, 13 Nov 2025 16:56:27 +0300 Subject: [PATCH 16/37] [TASK] issues #2 (https://github.com/SENATOROVAI/intro-cs/issues/2) Closes https://github.com/SENATOROVAI/intro-cs/issues/2 --- python/issues.ipynb | 126 +++++++++++++++++++++++-------------------- python/issues.py | 128 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 58 deletions(-) create mode 100644 python/issues.py diff --git a/python/issues.ipynb b/python/issues.ipynb index c2ce1fb1..b7a02e43 100644 --- a/python/issues.ipynb +++ b/python/issues.ipynb @@ -17,9 +17,11 @@ "source": [ "### Общие вопросы\n", "1. Что такое Issues на GitHub и для чего они используются?\n", - " - Это инструмент для отслеживания задач, багов, который используется для планирования и управления работой в репозиториях GitHub. \n", + " - Это инструмент для отслеживания и обсуждения различных задач, который используется для планирования и управления работой в репозиториях GitHub\n", + "\n", "2. Чем Issues отличаются от других инструментов управления задачами?\n", - " - Это простой и гибкий трекер задач, интегрированный с самим кодом, пул-реквестами, коммитами и проектами. В отличие от корпоративных систем (например, Jira), Issues не имеют избыточных настроек, категорий и атрибутов, а их кастомизация достигается с помощью меток, майлстонов, связанных задач и автоматизации через GitHub.\n", + " - Issues интегрированы с GitHub, пул-реквестами, коммитами и проектами\n", + "\n", "3. Какие основные компоненты (поля) есть у каждого Issue?\n", " - Title (Заголовок)\n", " - Description (Описание)\n", @@ -28,21 +30,22 @@ " - Milestone (Веха)\n", " - Projects (Связанные проекты)\n", " - Comments (Комментарии)\n", - " - Issue type (Тип, если настроено в организации)\n", + " - Issue type (Тип, если настроено)\n", " - State (Open/Closed — открыт/закрыт)\n", " - Linked issues / Pull requests (Связанные задачи или PR)\n", + " - Participants (участники)\n", "\n", "### Создание Issues\n", - "\n", - "1. Как создать новое Issue в репозитории?\n", + "4. Как создать новое Issue в репозитории?\n", " 1) Заходим в файл в репозитории \n", " 2) ЛКМ+shift-copy permalink\n", " 3) Issues - New issue - Выбираем тип Issues\n", " 4) Заполняем title и описание Issues\n", " 5) Issues - set milestone\n", " 6) Submit new issue\n", - "2. Какие данные рекомендуется указывать в описании Issue для лучшего понимания задачи?\n", - " - Указать ссылку на файл кода для issues\n", + "\n", + "5. Какие данные рекомендуется указывать в описании Issue для лучшего понимания задачи?\n", + " - Указать ссылку на файл кода\n", " - Описание и название файла кода\n", " - Описание проблемы или задачи\n", " - Шаги для воспроизведения (если это баг)\n", @@ -50,87 +53,94 @@ " - Скриншоты или логи при необходимости\n", " - Возможные решения или идеи\n", " - Ссылки на связанные задачи или обсуждения\n", - "3. Какие теги (labels) можно добавить к Issue? Какие из них стандартные?\n", - " - Можно добавить стандартные и кастомные\n", + "\n", + "6. Какие теги (labels) можно добавить к Issue? Какие из них стандартные?\n", + " - Можно добавить стандартные и кастомные теги\n", " - Стандартные: bug, enhancement, documentation, duplicate, good first issue, help wanted, invalid, question, wontfix\n", - "4. Как прикрепить Assignees (ответственных) к Issue?\n", + "\n", + "7. Как прикрепить Assignees (ответственных) к Issue?\n", " - Assignees - выбрать пользователя или группу\n", "\n", "### Работа с Issues\n", + "8. Как использовать Labels для классификации задач?\n", + " - Выставить теги по типу и приоритету в Labels в окне Issues\n", "\n", - "1. Как использовать Labels для классификации задач?\n", - " - Выставить теги в Labels в окне Issues и поставить нужные теги\n", - "2. Для чего нужен Milestone, и как связать его с Issue?\n", + "9. Для чего нужен Milestone, и как связать его с Issue?\n", " - Для того, чтобы привязать Issue к дате или определенному этапу\n", " - Выставить дату или этап в Milestone в окне Issues\n", - "3. Как привязать Issue к пул-реквесту (Pull Request)?\n", - " - При создании pull request или коммита указать номер Issue в #123, либо использовать ключевые слова (\"Closes #123\", \"Fixes #123\")\n", + "\n", + "10. Как привязать Issue к пул-реквесту (Pull Request)?\n", + " - В описании pull request указать #номер Issue или использовать ключевые слова (\"Closes #N\", \"Fixes #N\")\n", " - Можно вручную добавить связь через панель Linked issues\n", - "4. Как добавить комментарий к существующему Issue?\n", - " - В конце страницы Issue в поле комментария — написать текст и нажать \"Comment\", чтобы добавить сообщение\n", + "\n", + "11. Как добавить комментарий к существующему Issue?\n", + " - Внизу страницы Issue написать комментарий\n", "\n", "### Закрытие и завершение Issues\n", + "12. Как закрыть Issue вручную?\n", + " - Issues - Close issue\n", + "\n", + "13. Можно ли автоматически закрыть Issue с помощью сообщения в коммите или пул-реквесте? Как это сделать?\n", + " - Если в сообщении коммита или pull request использовать ключевые слова: \"Closes #номер\", \"Fixes #номер\", Resolves #номер\n", "\n", - "1. Как закрыть Issue вручную?\n", - " 1) Заходим в Issues\n", - " 2) Находим Файл\n", - " 3) Ветка - main\n", - " 4) Редактируем файл\n", - " 5) Commit changes\n", - " 6) Commit message - тайтл Issues, Extended description - closes (Номер ошибки)\n", - " 7) Sing off and commit changes\n", - "2. Можно ли автоматически закрыть Issue с помощью сообщения в коммите или пул-реквесте? Как это сделать?\n", - " - Если в сообщении коммита или pull request использовать ключевые слова: \"Closes #номер\", \"Fixes #номер\"\n", - "3. Как повторно открыть закрытое Issue, если работа ещё не завершена?\n", - " - Вкладка Issue - Выбрать Issue - Нажать \"Reopen issue\"\n", + "14. Как повторно открыть закрытое Issue, если работа ещё не завершена?\n", + " - Issue - Reopen issue\n", "\n", "### Фильтрация и поиск\n", + "15. Как найти все открытые или закрытые Issues в репозитории?\n", + " - Во вкладке Issues нажать \"Closed\" или в поиске написать is:open или is:closed для фильтрации\n", "\n", - "1. Как найти все открытые или закрытые Issues в репозитории?\n", - " - Во вкладке Issues по нажать \"Closed\" или в поиске, напишите is:open или is:closed для фильтрации.\n", - "2. Как использовать фильтры для поиска Issues по меткам, исполнителям или другим критериям?\n", - " - В поиске написать, например: label:bug assignee:username milestone:\"1.0.0\", либо использовать фильтры сверху (Labels, Assignee, Milestones, Projects).\n", - "3. Как сортировать Issues по приоритету, дате создания или другим параметрам?\n", + "16. Как использовать фильтры для поиска Issues по меткам, исполнителям или другим критериям?\n", + " - В поиске написать, например: label:bug, assignee:username, milestone:\"1.0.0\", либо использовать фильтры сверху (Labels, Assignee, Milestones, Projects)\n", + "\n", + "17. Как сортировать Issues по приоритету, дате создания или другим параметрам?\n", " - В меню (\"Sort\") на вкладке Issues: доступны опции по дате создания, обновления, количеству комментариев и т.д. Приоритет обычно выставляется ярлыками (\"priority: high\")\n", "\n", "### Интеграции и автоматизация\n", + "18. Как настроить автоматические уведомления о новых или изменённых Issues?\n", + " - В репозитории - Watch\n", + " - В Issue - Subscribe\n", + "\n", + "19. Что такое Projects в контексте GitHub, и как связать их с Issues?\n", + " - Projects - это доски для визуального управления задачами. Issues можно добавлять в проекты для отслеживания статуса и автоматизации рабочих процессов\n", "\n", - "1. Как настроить автоматические уведомления о новых или изменённых Issues?\n", - " - В репозитории нажать \"Watch\" — выбрать интересующий вариант уведомлений\n", - "2. Что такое Projects в контексте GitHub, и как связать их с Issues?\n", - " - Projects - это доски для визуального управления задачами. Issues можно добавлять в проекты для отслеживания статуса и автоматизации рабочих процессов.\n", - "3. Какие сторонние инструменты можно использовать для автоматизации работы с Issues (например, боты, Webhooks)?\n", + "20. Какие сторонние инструменты можно использовать для автоматизации работы с Issues (например, боты, Webhooks)?\n", " - GitHub Actions (боты и скрипты для автоматизации работ)\n", " - Webhooks для интеграции с внешними сервисами\n", - " - Сторонние боты типа probot, интеграция с Jira, Trello\n", + " - Probot - платформа для GitHub ботов, \n", + " - Интеграция с Jira, Trello\n", "\n", "### Коллаборация\n", + "21. Как упомянуть другого пользователя в комментарии к Issue?\n", + " - @username\n", "\n", - "1. Как упомянуть другого пользователя в комментарии к Issue?\n", - " - Использовать @username\n", - "2. Как запросить дополнительные данные или уточнения у автора Issue?\n", + "22. Как запросить дополнительные данные или уточнения у автора Issue?\n", " - Использовать упоминание в комментарии. Можно дополнительно добавить label \"needs info\"\n", - "3. Что делать, если Issue неактуально или его нужно объединить с другим?\n", - " - Для устаревших задач — закройте Issue с пометкой: \"Invalid\", \"Outdated\", кратко описав причину\n", - "Для объединения — оставьте ссылку на дубликат (\"Duplicate of #номер\"), закройте и используйте ярлык \"duplicate\"\n", + "\n", + "23. Что делать, если Issue неактуально или его нужно объединить с другим?\n", + " - Для устаревших задач закрыть Issue с пометкой: \"Invalid\", \"Outdated\"\n", + " - Для объединения задач закрыть Issue с пометкой: \"Duplicate of #номер\"\n", "\n", "### Практические аспекты\n", + "24. Как использовать шаблоны для создания Issues?\n", + " - Создать файл .github/ISSUE_TEMPLATE.md\n", + " - Если несколько несколько шаблонов, то создать папку .github/ISSUE_TEMPLATE/ добавить в нее файлы шаблонов с расширением .md\n", + " - В Settings репозитория через кнопку \"Set up templates\" можно управлять шаблонами\n", + "\n", + "25. Что такое Linked Issues, и как создать связь между задачами?\n", + " - Это задачи, логически связанные между собой (например, подзадачи). Связать можно с помощью панели \"Linked issues\", а также упоминанием через #номер в тексте\n", "\n", - "1. Как использовать шаблоны для создания Issues?\n", - " - В разделе Settings репозитория можно добавить шаблоны Issue (Issue Templates) для разных типов задач\n", - "2. Что такое Linked Issues, и как создать связь между задачами?\n", - " - Linked Issues - это задачи, логически связанные между собой (например, подзадачи). Связать можно с помощью панели \"Linked issues\", а также упоминания через #номер в тексте\n", - "3. Какие метрики (например, время выполнения) можно отслеживать с помощью Issues?\n", + "26. Какие метрики (например, время выполнения) можно отслеживать с помощью Issues?\n", " - Количество открытых/закрытых задач\n", " - Время выполнения задачи\n", " - Активность по assigned/closed issues\n", - " - Число комментариев\n", - "4. Какие best practices рекомендуются при работе с Issues в команде?\n", - " - Используйте шаблоны и метки для стандартизации.\n", - " - Назначайте ответственных\n", - " - Описывайте задачи понятно, структурированно и без двусмысленности\n", - " - Переводите устаревшие/дублирующие Issues в архив\n", - " - Интегрируйте Issues в pipeline разработки через Pull Request’ы, Projects и автоматизацию" + "\n", + "27. Какие best practices рекомендуются при работе с Issues в команде?\n", + " - Использовать шаблоны и метки для стандартизации\n", + " - Назначать ответственных\n", + " - Описывать задачи понятно, структурировано и без двусмысленности\n", + " - Переводить устаревшие/дублирующие Issues в архив\n", + " - Использовать ссылки и ключевые слова" ] } ], diff --git a/python/issues.py b/python/issues.py new file mode 100644 index 00000000..455902b9 --- /dev/null +++ b/python/issues.py @@ -0,0 +1,128 @@ +"""[TASK] issues #2.""" + +# ### Общие вопросы +# 1. Что такое Issues на GitHub и для чего они используются? +# - Это инструмент для отслеживания и обсуждения различных задач, который используется для планирования и управления работой в репозиториях GitHub +# +# 2. Чем Issues отличаются от других инструментов управления задачами? +# - Issues интегрированы с GitHub, пул-реквестами, коммитами и проектами +# +# 3. Какие основные компоненты (поля) есть у каждого Issue? +# - Title (Заголовок) +# - Description (Описание) +# - Labels (Метки) +# - Assignees (Ответственные) +# - Milestone (Веха) +# - Projects (Связанные проекты) +# - Comments (Комментарии) +# - Issue type (Тип, если настроено) +# - State (Open/Closed — открыт/закрыт) +# - Linked issues / Pull requests (Связанные задачи или PR) +# - Participants (участники) +# +# ### Создание Issues +# 4. Как создать новое Issue в репозитории? +# 1) Заходим в файл в репозитории +# 2) ЛКМ+shift-copy permalink +# 3) Issues - New issue - Выбираем тип Issues +# 4) Заполняем title и описание Issues +# 5) Issues - set milestone +# 6) Submit new issue +# +# 5. Какие данные рекомендуется указывать в описании Issue для лучшего понимания задачи? +# - Указать ссылку на файл кода +# - Описание и название файла кода +# - Описание проблемы или задачи +# - Шаги для воспроизведения (если это баг) +# - Ожидаемое и фактическое поведение +# - Скриншоты или логи при необходимости +# - Возможные решения или идеи +# - Ссылки на связанные задачи или обсуждения +# +# 6. Какие теги (labels) можно добавить к Issue? Какие из них стандартные? +# - Можно добавить стандартные и кастомные теги +# - Стандартные: bug, enhancement, documentation, duplicate, good first issue, help wanted, invalid, question, wontfix +# +# 7. Как прикрепить Assignees (ответственных) к Issue? +# - Assignees - выбрать пользователя или группу +# +# ### Работа с Issues +# 8. Как использовать Labels для классификации задач? +# - Выставить теги по типу и приоритету в Labels в окне Issues +# +# 9. Для чего нужен Milestone, и как связать его с Issue? +# - Для того, чтобы привязать Issue к дате или определенному этапу +# - Выставить дату или этап в Milestone в окне Issues +# +# 10. Как привязать Issue к пул-реквесту (Pull Request)? +# - В описании pull request указать #номер Issue или использовать ключевые слова ("Closes #N", "Fixes #N") +# - Можно вручную добавить связь через панель Linked issues +# +# 11. Как добавить комментарий к существующему Issue? +# - Внизу страницы Issue написать комментарий +# +# ### Закрытие и завершение Issues +# 12. Как закрыть Issue вручную? +# - Issues - Close issue +# +# 13. Можно ли автоматически закрыть Issue с помощью сообщения в коммите или пул-реквесте? Как это сделать? +# - Если в сообщении коммита или pull request использовать ключевые слова: "Closes #номер", "Fixes #номер", Resolves #номер +# +# 14. Как повторно открыть закрытое Issue, если работа ещё не завершена? +# - Issue - Reopen issue +# +# ### Фильтрация и поиск +# 15. Как найти все открытые или закрытые Issues в репозитории? +# - Во вкладке Issues нажать "Closed" или в поиске написать is:open или is:closed для фильтрации +# +# 16. Как использовать фильтры для поиска Issues по меткам, исполнителям или другим критериям? +# - В поиске написать, например: label:bug, assignee:username, milestone:"1.0.0", либо использовать фильтры сверху (Labels, Assignee, Milestones, Projects) +# +# 17. Как сортировать Issues по приоритету, дате создания или другим параметрам? +# - В меню ("Sort") на вкладке Issues: доступны опции по дате создания, обновления, количеству комментариев и т.д. Приоритет обычно выставляется ярлыками ("priority: high") +# +# ### Интеграции и автоматизация +# 18. Как настроить автоматические уведомления о новых или изменённых Issues? +# - В репозитории - Watch +# - В Issue - Subscribe +# +# 19. Что такое Projects в контексте GitHub, и как связать их с Issues? +# - Projects - это доски для визуального управления задачами. Issues можно добавлять в проекты для отслеживания статуса и автоматизации рабочих процессов +# +# 20. Какие сторонние инструменты можно использовать для автоматизации работы с Issues (например, боты, Webhooks)? +# - GitHub Actions (боты и скрипты для автоматизации работ) +# - Webhooks для интеграции с внешними сервисами +# - Probot - платформа для GitHub ботов, +# - Интеграция с Jira, Trello +# +# ### Коллаборация +# 21. Как упомянуть другого пользователя в комментарии к Issue? +# - @username +# +# 22. Как запросить дополнительные данные или уточнения у автора Issue? +# - Использовать упоминание в комментарии. Можно дополнительно добавить label "needs info" +# +# 23. Что делать, если Issue неактуально или его нужно объединить с другим? +# - Для устаревших задач закрыть Issue с пометкой: "Invalid", "Outdated" +# - Для объединения задач закрыть Issue с пометкой: "Duplicate of #номер" +# +# ### Практические аспекты +# 24. Как использовать шаблоны для создания Issues? +# - Создать файл .github/ISSUE_TEMPLATE.md +# - Если несколько несколько шаблонов, то создать папку .github/ISSUE_TEMPLATE/ добавить в нее файлы шаблонов с расширением .md +# - В Settings репозитория через кнопку "Set up templates" можно управлять шаблонами +# +# 25. Что такое Linked Issues, и как создать связь между задачами? +# - Это задачи, логически связанные между собой (например, подзадачи). Связать можно с помощью панели "Linked issues", а также упоминанием через #номер в тексте +# +# 26. Какие метрики (например, время выполнения) можно отслеживать с помощью Issues? +# - Количество открытых/закрытых задач +# - Время выполнения задачи +# - Активность по assigned/closed issues +# +# 27. Какие best practices рекомендуются при работе с Issues в команде? +# - Использовать шаблоны и метки для стандартизации +# - Назначать ответственных +# - Описывать задачи понятно, структурировано и без двусмысленности +# - Переводить устаревшие/дублирующие Issues в архив +# - Использовать ссылки и ключевые слова From 209a2ca43d6a8e2a66acceb9f8ec526ca55512e4 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Thu, 13 Nov 2025 17:16:59 +0300 Subject: [PATCH 17/37] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=83=D0=B6=D0=BD=D1=8B=D0=B5=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- github/quiz.py | 165 ---------------------------------------------- python/cpython.py | 60 ----------------- 2 files changed, 225 deletions(-) delete mode 100644 github/quiz.py delete mode 100644 python/cpython.py diff --git a/github/quiz.py b/github/quiz.py deleted file mode 100644 index 318556dd..00000000 --- a/github/quiz.py +++ /dev/null @@ -1,165 +0,0 @@ -"""[TASK] Контрибьютинг в Open Source #8.""" - -# GitHub -# 1.1. Что такое GitHub? -# - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions. -# -# 1.2. Как GitHub связан с Git? -# Git — это распределенная система контроля версий -# GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями. -# - Основные различия:​ -# Git — программное обеспечение для локального управления версиями -# GitHub — облачный сервис для хостинга Git-репозиториев с функциями совместной работы -# Git работает через командную строку, GitHub предоставляет графический интерфейс -# Git бесплатен и с открытым кодом, GitHub принадлежит Microsoft и имеет платные тарифы -# -# 1.3. Чем отличается fork репозитория от его клонирования (clone)? -# - Fork (форк):​ -# Создает полностью независимую копию репозитория на вашем GitHub-аккаунте -# Используется для внесения вкладов в чужие проекты через pull requests -# Оригинальные разработчики не имеют доступа к вашему форку -# - Clone (клонирование):​ -# Создает локальную копию репозитория на вашем компьютере -# Копия остается связанной с исходным репозиторием -# Вы можете синхронизировать изменения через git push и git pull -# -# 1.4. Зачем нужны и как работают pull requests? -# - Pull request (PR) — это механизм для предложения изменений в проект. -# - Назначение:​ -# Уведомить других разработчиков о завершенной функции -# Инициировать code review -# Обсудить предлагаемые изменения -# Получить обратную связь перед слиянием кода -# - Как работает:​ -# Разработчик создает ветку с изменениями -# Отправляет ветку в репозиторий (свой fork или общий) -# Открывает pull request через GitHub -# Команда обсуждает и проверяет код -# Вносятся дополнительные изменения при необходимости -# Сопровождающий проекта принимает и сливает изменения -# -# 1.5. GitHub использует ваш почтовый адрес для привязки ваших Git коммитов к вашей учётной записи? -# Да -# -# 1.6 Какая команда генерирует SSH ключ для Доступа по SSH к репозиторию (Рисунок 83) -# - Команда для генерации SSH-ключа:​​ -# ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -# - Или с использованием более современного алгоритма: -# ssh-keygen -t ed25519 -C "your_email@example.com" -# Параметры:​ -# -t — тип ключа (rsa, ed25519) -# -b 4096 — размер ключа в битах (для RSA) -# -C — комментарий (обычно ваш email) -# -# Внесение собственного вклада в проекты -# 2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117) -# Как открыть запрос слияния: -# Создайте первую ветку (feature-a) из основной ветки (main/master). -# Откройте Merge request A из feature-a в main. -# Создайте вторую ветку (feature-b) из feature-a (а не из main). -# Откройте Merge request B из feature-b в main -# Зачем нужно: -# Разделение сложных задач -# Параллельная разработка -# Управление порядком интеграции -# -# Рабочий процесс с использованием GitHub -# 3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект. -# Форкнуть проект -# Клонировать форк локально -# Создать отдельную ветку для изменений -# Внести изменения -# Закоммитить изменения -# Отправить изменения в свой форк -# Создать Pull Request -# Подождать проверки -# -# 3.1 Какие практики принято соблюдать при создании Pull Request чтобы закрыть автоматический issues? -# В описании pull request использовать ключевые слова и номер issue: -# Fixes #123 -# Closes #456 -# Resolves #789 -# Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues? -# тоже самое, что и в пулл реквесте -# -# 3.2 Как отклонить/закрыть пул реквест? (предоставьте скриншот где это в гитхабе) -# ![image.png](attachment:image.png) -# -# 3.3 Перед отправкой пул реквеста нужно ли создавать ишьюс? -# Нет, это не обязательно, но зависит от практик конкретного проекта. -# 3.4 В какой вкладке можно посмотреть список изменений который был в пул реквесте? (Рисунок 92) -# Files changed -# 3.5 В какой вкладке находится страница обсуждений пул реквеста? (Рисунок 94) -# Conversation -# -# Создание запроса на слияние -# 4 Можно ли открыть пул реквест, если вы ничего не вносили в FORK? -# Нет -# -# 4.1 Что нужно сделать чтобы открыть пул реквест? (Рисунок 90) -# Сделать форк (копию) нужного репозитория на GitHub и склонировать его себе локально. -# Создать новую ветку в локальном репозитории для ваших изменений. -# Внести необходимые правки в код или файлы и закоммитить изменения в этой ветке. -# Отправить (push) вашу ветку с изменениями в ваш форк на GitHub. -# На сайте GitHub перейти в ваш форк, и после пуша появится кнопка "Compare & pull request" — нажать ее. -# Убедиться, что выбраны правильные ветки — откуда (ваша ветка) и куда (обычно main или master основного репозитория). -# Написать заголовок и описание к пул реквесту, чтобы объяснить суть изменений. -# Создать сам пул реквест кнопкой "Create pull request". -# -# 4.2 Что нужно сделать Если ваш Форк устарел? -# Нужно синхронизировать его с оригинальным репозиторием: -# Добавить исходный репозиторий как удалённый с именем «upstream» -# Получить все изменения из оригинального репозитория -# Переключиться на нужную ветку -# Синхронизировать изменения -# Отправить изменения в свой форк -# -# 4.3 Что нужно сделать если в пул реквесте имеются конфликты слияния (Рисунок 96) -# Добавляем исходный репозиторий как удалённый с именем «upstream» -# Получаем последние изменения из него -# Сливаем основную ветку в нашу тематическую -# Исправляем указанный конфликт -# Отправляем изменения в ту же тематическую ветку -# -# Отрывки кода -# 5 Что нужно сделать Для добавления отрывка кода в комментарии к ишьюсу? (Рисунок 104) -# Для добавления отрывка кода следует обрамить его обратными кавычками -# 5.1 На какую клавишу нажать клавишу чтобы выделенный текст был включён как цитата в ваш комментарий?(Рисунок 105) -# Клавиша "r" -# 5.2 Как вставить картинку в ишьюс? (Рисунок 108) -# Перетащить картинку или скопировать изображение -# -# Поддержание GitHub репозитория в актуальном состоянии -# 6 Как понять что ваш форк устарел? -# При появлении в оригинальном репозитории новых коммитов GitHub информирует вас при помощи сообщения: This branch is 5 commits behind progit:master -# -# 6.1 Как обновить форк? -# Sync fork - Update branch -# -# Добавление участников -# 7 Как добавить участников в ваш репозиторий, чтобы команда могла работать над одним репозиторием? (Рисунок 112) -# «Settings» - «Collaborators» - «Add collaborator» -# -# Упоминания и уведомления -# 8 Какой символ нужен для упоминания кого-либо? (Рисунок 118) -# Символ "@" -# 8.1 Где находится Центр уведомлений, напишите ссылку (Рисунок 121) -# https://github.com/notifications -# -# Особенные файлы -# 9 Что такое и зачем нужен файл README -# Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику. -# -# 9.1 Что такое и зачем нужен файл CONTRIBUTING (Рисунок 122) -# Файл CONTRIBUTING — это документ в репозитории, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта. Основная цель этого файла — помочь новым участникам понять, как правильно взаимодействовать с проектом и что нужно делать для успешного добавления изменений. -# Он описывает процесс участия: как клонировать репозиторий, создавать ветки, вносить изменения и отправлять их на проверку через pull requests. В файле устанавливаются стандарты и требования по коду, формату коммитов, стилю программирования, тестированию и другим аспектам разработки. -# -# Управление проектом -# 10 Как изменить основную ветку (Рисунок 123) -# «Options» - Default branch -# 10.1 Как передать проект? какая кнопка? (рисунок 124) -# «Options» - «Transfer ownership» -# 10.2 Что такое файл .gitignore? -# Файл .gitignore — это текстовый файл в системе контроля версий Git, который содержит список файлов и каталогов, которые Git должен игнорировать и не отслеживать. -# -# diff --git a/python/cpython.py b/python/cpython.py deleted file mode 100644 index 33d716b3..00000000 --- a/python/cpython.py +++ /dev/null @@ -1,60 +0,0 @@ -"""[TASK] Cpython #4.""" - -# 1. Что такое CPython и чем он отличается от Python? -# - Реализация языка Python, написанная на C. -# - Python - это спецификация языка программирования, а CPython - эго его интерпретатор. -# 3. Сколько существует реализаций Python, и какая из них самая популярная? -# - 6 реализаций: CPython, PyPy, Jython, IronPython, Brython, Nuitka. -# - CPython самая популярная реализация. -# 4. На каком языке написан CPython? -# - С. -# 5. Кто создал CPython? -# - Гвидо ван Россум. -# 6. Почему Python считается быстрым, несмотря на то, что это интерпретируемый язык? -# - Интерпретатор компилирует исходный код в байткод, который затем исполняется виртуальной машиной на С. -# 7. Напишите путь к Интерпретатору CPython на вашем компьютере. -# - C:\Users\nigma.DESKTOP-55Q3CA4\AppData\Local\Programs\Python\Python314\python.exe -# 8. Что содержится в папке include в CPython? -# - файлы на языке C, необходимые для компиляции расширений и взаимодействия с ядром интерпретатора Python. -# 9. Где можно найти исходный код CPython дайте ссылку на репозиторий гитхаб. -# - https://github.com/python/cpython -# 10. Как работает интерпретатор CPython при выполнении кода? -# 1. Читает исходный код, проверяет его синтаксис и форматирование. -# 2. Трансформирует исходный код в байт-код. -# 3. Передает байт-код виртуальной машине. -# 11. Какая команда используется для запуска файла с помощью CPython? -# - python имя_файла.py. -# 12. Можно ли запускать текстовые файлы через интерпретатор Python? Почему? -# - Можно, если в них содержится правильный Python код. -# - Интерпретатор читает последовательность символов из файла и пытается выполнить их как команды на языке Python. -# 13. Как указать путь к интерпретатору и файлу для выполнения кода? -# - ПКМ - свойства - расположение файла. -# - Вставить в командную строку. -# 14. Чем PyPy отличается от CPython? -# - Работает быстрее за счёт JIT-компиляции. -# 15. Почему PyPy не может использоваться для всех проектов на Python? -# - Есть проблемы с совместимостью с библиотеками, использующими C-расширения, такими как NumPy или SciPy. -# 16. Где можно скачать PyPy? -# - https://pypy.org/download.html -# 17. Как установить PyPy после скачивания? -# - Распаковать архив. -# 18. Как запустить файл с помощью PyPy? -# - Путь к интерпретатору PyPy пробел путь к файлу в командной строке. -# 19. Почему PyPy выполняет код быстрее, чем CPython? -# - Преобразует в машинный код наиболее часто используемые компоненты кода, и оптимизирует его. -# -# Практические задания -# -# 2. Исследование структуры CPython -# Найдите папку, где установлен Python (например, через команду which python в терминале или свойства ярлыка). -# Откройте папку include и изучите её содержимое. Какое количество файлов на C там есть? -# - 79. -# 5. Сравнение производительности CPython и PyPy -# - CPython: -# Result: 49999995000000 -# Execution time: 0.4225647449493408 seconds -# - PyPy: -# Result: 49999995000000 -# Execution time: 0.006410121917724609 seconds -# - Вывод: -# PyPy быстрее CPython примерно в 65 раз. From 57bcad0ef0cf9edc605ca5fd3dd1830dc4423c63 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Thu, 13 Nov 2025 23:29:17 +0300 Subject: [PATCH 18/37] =?UTF-8?q?[TASK]=20=D0=9A=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D0=B1=D1=8C=D1=8E=D1=82=D0=B8=D0=BD=D0=B3=20=D0=B2=20Ope?= =?UTF-8?q?n=20Source=20=20#8=20(https://github.com/SENATOROVAI/intro-cs/i?= =?UTF-8?q?ssues/8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/SENATOROVAI/intro-cs/issues/8 --- github/opensource.ipynb | 72 +++++++++++++++ github/opensource.py | 40 +++++++++ github/quiz.ipynb | 193 +++++++++++++++++----------------------- github/quiz.py | 136 ++++++++++++++++++++++++++++ 4 files changed, 330 insertions(+), 111 deletions(-) create mode 100644 github/opensource.ipynb create mode 100644 github/opensource.py create mode 100644 github/quiz.py diff --git a/github/opensource.ipynb b/github/opensource.ipynb new file mode 100644 index 00000000..b0606968 --- /dev/null +++ b/github/opensource.ipynb @@ -0,0 +1,72 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "36215b23", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"[TASK] Контрибьютинг в Open Source #8.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "a81fca61", + "metadata": {}, + "source": [ + "Проект в который вы отправили пул реквест, попадает под определение опенсорса:\n", + "\n", + "Есть ли у него лицензия? Обычно в корне репозитория находится файл LICENSE.\n", + " - Да\n", + "Напишите название понравившейся компании и ссылку на репозиторий\n", + " - https://github.com/yandex\n", + "Проект активно принимает стороннюю помощь?\n", + " - Да\n", + "Напишите второе улучшение которое вы сделали\n", + " - Исправил опечатку в коде\n", + "Посмотрите на коммиты в основной ветке, напишите общее количество\n", + " - 27\n", + "Когда был последний коммит?\n", + " - 12.11.25\n", + "Сколько контрибьюторов у проекта?\n", + " - 6\n", + "Как часто люди коммитят в репозиторий? (На GitHub выяснить это можно, кликнув по ссылке «Commits» в верхней панели.)\n", + " - в среднем 3 коммита в месяц\n", + "Сколько сейчас открытых ишью?\n", + " - 7\n", + "Быстро ли мейнтейнеры реагируют на ишью после того, когда они открываются?\n", + " - Нет \n", + "Ведётся ли активное обсуждение ишью?\n", + " - Да\n", + "Есть ли недавно созданные ишью?\n", + " - Да\n", + "Есть ли закрытые ишью? (На странице Issues GitHub-репозитория щелкните на вкладку «Closed», чтобы увидеть закрытые ишью.)\n", + " - Да\n", + "Сколько сейчас открытых пул-реквестов?\n", + " - 13\n", + "Быстро ли мейнтейнеры реагируют на пул-реквесты после их открытия?\n", + " - Да\n", + "Ведётся ли активное обсуждение пул-реквестов?\n", + " - Нет\n", + "Есть ли недавно отправленные пул-реквесты?\n", + " - Да\n", + "Как давно были объединены пул-реквесты? (На странице Pull Request GitHub-репозитория щелкните на вкладку «Closed», чтобы увидеть закрытые пул-реквесты.)\n", + " - 12 часов" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/github/opensource.py b/github/opensource.py new file mode 100644 index 00000000..88e7d983 --- /dev/null +++ b/github/opensource.py @@ -0,0 +1,40 @@ +"""[TASK] Контрибьютинг в Open Source #8.""" + +# Проект в который вы отправили пул реквест, попадает под определение опенсорса: +# +# Есть ли у него лицензия? Обычно в корне репозитория находится файл LICENSE. +# - Да +# Напишите название понравившейся компании и ссылку на репозиторий +# - https://github.com/yandex +# Проект активно принимает стороннюю помощь? +# - Да +# Напишите второе улучшение которое вы сделали +# - Исправил опечатку в коде +# Посмотрите на коммиты в основной ветке, напишите общее количество +# - 27 +# Когда был последний коммит? +# - 12.11.25 +# Сколько контрибьюторов у проекта? +# - 6 +# Как часто люди коммитят в репозиторий? (На GitHub выяснить это можно, кликнув по ссылке «Commits» в верхней панели.) +# - в среднем 3 коммита в месяц +# Сколько сейчас открытых ишью? +# - 7 +# Быстро ли мейнтейнеры реагируют на ишью после того, когда они открываются? +# - Нет +# Ведётся ли активное обсуждение ишью? +# - Да +# Есть ли недавно созданные ишью? +# - Да +# Есть ли закрытые ишью? (На странице Issues GitHub-репозитория щелкните на вкладку «Closed», чтобы увидеть закрытые ишью.) +# - Да +# Сколько сейчас открытых пул-реквестов? +# - 13 +# Быстро ли мейнтейнеры реагируют на пул-реквесты после их открытия? +# - Да +# Ведётся ли активное обсуждение пул-реквестов? +# - Нет +# Есть ли недавно отправленные пул-реквесты? +# - Да +# Как давно были объединены пул-реквесты? (На странице Pull Request GitHub-репозитория щелкните на вкладку «Closed», чтобы увидеть закрытые пул-реквесты.) +# - 12 часов diff --git a/github/quiz.ipynb b/github/quiz.ipynb index e681c9fe..39b57c59 100644 --- a/github/quiz.ipynb +++ b/github/quiz.ipynb @@ -20,167 +20,138 @@ "id": "ed8d0ce1", "metadata": {}, "source": [ - "GitHub\n", + "### GitHub\n", "1.1. Что такое GitHub?\n", - " - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions.\n", + " - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions\n", "\n", "1.2. Как GitHub связан с Git?\n", - " Git — это распределенная система контроля версий \n", - " GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями.\n", - " - Основные различия:​\n", - " Git — программное обеспечение для локального управления версиями\n", - " GitHub — облачный сервис для хостинга Git-репозиториев с функциями совместной работы\n", - " Git работает через командную строку, GitHub предоставляет графический интерфейс\n", - " Git бесплатен и с открытым кодом, GitHub принадлежит Microsoft и имеет платные тарифы\n", + " - Git — это распределенная система контроля версий \n", + " - GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями\n", "\n", "1.3. Чем отличается fork репозитория от его клонирования (clone)?\n", - " - Fork (форк):​\n", - " Создает полностью независимую копию репозитория на вашем GitHub-аккаунте\n", - " Используется для внесения вкладов в чужие проекты через pull requests\n", - " Оригинальные разработчики не имеют доступа к вашему форку\n", - " - Clone (клонирование):​\n", - " Создает локальную копию репозитория на вашем компьютере\n", - " Копия остается связанной с исходным репозиторием\n", - " Вы можете синхронизировать изменения через git push и git pull\n", + " - Fork создает полностью независимую копию репозитория на GitHub-аккаунте\n", + " - Clone создает локальную копию репозитория\n", "\n", "1.4. Зачем нужны и как работают pull requests?\n", - " - Pull request (PR) — это механизм для предложения изменений в проект.\n", - " - Назначение:​\n", - " Уведомить других разработчиков о завершенной функции\n", - " Инициировать code review\n", - " Обсудить предлагаемые изменения\n", - " Получить обратную связь перед слиянием кода\n", - " - Как работает:​\n", - " Разработчик создает ветку с изменениями\n", - " Отправляет ветку в репозиторий (свой fork или общий)\n", - " Открывает pull request через GitHub\n", - " Команда обсуждает и проверяет код\n", - " Вносятся дополнительные изменения при необходимости\n", - " Сопровождающий проекта принимает и сливает изменения\n", + " - Pull request (PR) — это механизм слияния своей ветки с основной веткой репозитория для внесения изменений\n", + " - Pull request нужен для ​code review и обсуждения изменений перед слиянием\n", + "\n", + " - Как работает pull request:​\n", + " - Разработчик создает ветку с изменениями\n", + " - Отправляет ветку в репозиторий\n", + " - Открывает pull request через GitHub\n", + " - Команда обсуждает и проверяет код\n", + " - Затем ветки сливают\n", "\n", "1.5. GitHub использует ваш почтовый адрес для привязки ваших Git коммитов к вашей учётной записи?\n", - " Да\n", + " - Да\n", "\n", "1.6 Какая команда генерирует SSH ключ для Доступа по SSH к репозиторию (Рисунок 83)\n", - " - Команда для генерации SSH-ключа:​​\n", - " ssh-keygen -t rsa -b 4096 -C \"your_email@example.com\"\n", - " - Или с использованием более современного алгоритма:\n", - " ssh-keygen -t ed25519 -C \"your_email@example.com\"\n", - " Параметры:​\n", - " -t — тип ключа (rsa, ed25519)\n", - " -b 4096 — размер ключа в битах (для RSA)\n", - " -C — комментарий (обычно ваш email)\n", - "\n", - "Внесение собственного вклада в проекты\n", + " - ssh-keygen \n", + "\n", + "### Внесение собственного вклада в проекты\n", "2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117)\n", - " Как открыть запрос слияния:\n", - " Создайте первую ветку (feature-a) из основной ветки (main/master).\n", - " Откройте Merge request A из feature-a в main.\n", - " Создайте вторую ветку (feature-b) из feature-a (а не из main).\n", - " Откройте Merge request B из feature-b в main\n", - " Зачем нужно:\n", - " Разделение сложных задач\n", - " Параллельная разработка\n", - " Управление порядком интеграции\n", - "\n", - "Рабочий процесс с использованием GitHub\n", - "3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект.\n", - " Форкнуть проект\n", - " Клонировать форк локально\n", - " Создать отдельную ветку для изменений\n", - " Внести изменения\n", - " Закоммитить изменения\n", - " Отправить изменения в свой форк\n", - " Создать Pull Request\n", - " Подождать проверки\n", + " - Нужно сделать Pull request, указав другой Pull request\n", + " - Это нужно для того, чтобы обеспечить правильный порядок слияния или избежать конфликтов и ошибок\n", + "\n", + "### Рабочий процесс с использованием GitHub\n", + "3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект\n", + " - Форкнуть проект\n", + " - Клонировать форк локально\n", + " - Создать отдельную ветку для изменений\n", + " - Внести изменения\n", + " - Закоммитить изменения\n", + " - Отправить изменения в свой форк\n", + " - Создать Pull Request\n", + " - Подождать проверки\n", "\n", "3.1 Какие практики принято соблюдать при создании Pull Request чтобы закрыть автоматический issues?\n", - " В описании pull request использовать ключевые слова и номер issue:\n", - " Fixes #123\n", - " Closes #456\n", - " Resolves #789\n", - "Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues?\n", - " тоже самое, что и в пулл реквесте\n", + " - В описании pull request использовать ключевые слова и номер issue:\n", + " - Fixes #номер, Closes #номер, Resolves #номер\n", + "\n", + " - Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues?\n", + " - Использовать те же ключевые слова и номер issue в сообщении коммита\n", "\n", "3.2 Как отклонить/закрыть пул реквест? (предоставьте скриншот где это в гитхабе)\n", " ![image.png](attachment:image.png)\n", "\n", "3.3 Перед отправкой пул реквеста нужно ли создавать ишьюс?\n", - " Нет, это не обязательно, но зависит от практик конкретного проекта.\n", + " - Нет, это не обязательно, но зависит от практик конкретного проекта\n", + "\n", "3.4 В какой вкладке можно посмотреть список изменений который был в пул реквесте? (Рисунок 92)\n", - " Files changed\n", + " - Files changed\n", + "\n", "3.5 В какой вкладке находится страница обсуждений пул реквеста? (Рисунок 94)\n", - " Conversation\n", + " - Conversation\n", "\n", - "Создание запроса на слияние\n", + "### Создание запроса на слияние\n", "4 Можно ли открыть пул реквест, если вы ничего не вносили в FORK?\n", - " Нет\n", + " - Нет\n", "\n", "4.1 Что нужно сделать чтобы открыть пул реквест? (Рисунок 90)\n", - " Сделать форк (копию) нужного репозитория на GitHub и склонировать его себе локально.\n", - " Создать новую ветку в локальном репозитории для ваших изменений.\n", - " Внести необходимые правки в код или файлы и закоммитить изменения в этой ветке.\n", - " Отправить (push) вашу ветку с изменениями в ваш форк на GitHub.\n", - " На сайте GitHub перейти в ваш форк, и после пуша появится кнопка \"Compare & pull request\" — нажать ее.\n", - " Убедиться, что выбраны правильные ветки — откуда (ваша ветка) и куда (обычно main или master основного репозитория).\n", - " Написать заголовок и описание к пул реквесту, чтобы объяснить суть изменений.\n", - " Создать сам пул реквест кнопкой \"Create pull request\".\n", + " - Сделать форк репозитория и склонировать его\n", + " - Создать новую ветку в репозитории для изменений\n", + " - Внести изменения и закоммитить изменения в этой ветке\n", + " - Отправить (push) ветку с изменениями в форк на GitHub\n", + " - На GitHub после пуша появится нажать \"Compare & pull request\"\n", + " - Создать пул реквест \"Create pull request\"\n", "\n", "4.2 Что нужно сделать Если ваш Форк устарел?\n", - " Нужно синхронизировать его с оригинальным репозиторием:\n", - " Добавить исходный репозиторий как удалённый с именем «upstream»\n", - " Получить все изменения из оригинального репозитория\n", - " Переключиться на нужную ветку\n", - " Синхронизировать изменения\n", - " Отправить изменения в свой форк\n", + " - Обновить форк\n", + " - Добавить исходный репозиторий как удалённый с именем «upstream»\n", + " - Получить (fetch) все изменения из оригинального репозитория\n", + " - Переключиться на основную ветку\n", + " - Синхронизировать изменения merge или rebase\n", + " - Отправить (push) изменения в свой форк\n", "\n", "4.3 Что нужно сделать если в пул реквесте имеются конфликты слияния (Рисунок 96)\n", - " Добавляем исходный репозиторий как удалённый с именем «upstream»\n", - " Получаем последние изменения из него\n", - " Сливаем основную ветку в нашу тематическую\n", - " Исправляем указанный конфликт\n", - " Отправляем изменения в ту же тематическую ветку\n", + " - Разрешить конфликты и запушить исправленную версию\n", " \n", - "Отрывки кода\n", + "### Отрывки кода\n", "5 Что нужно сделать Для добавления отрывка кода в комментарии к ишьюсу? (Рисунок 104)\n", - " Для добавления отрывка кода следует обрамить его обратными кавычками\n", + " - Отметить его обратными кавычками\n", + "\n", "5.1 На какую клавишу нажать клавишу чтобы выделенный текст был включён как цитата в ваш комментарий?(Рисунок 105)\n", - " Клавиша \"r\"\n", + " - Клавиша \"r\" или символ \">\"\n", + "\n", "5.2 Как вставить картинку в ишьюс? (Рисунок 108)\n", - " Перетащить картинку или скопировать изображение\n", + " - Перетащить картинку или скопировать изображение\n", "\n", - "Поддержание GitHub репозитория в актуальном состоянии\n", + "### Поддержание GitHub репозитория в актуальном состоянии\n", "6 Как понять что ваш форк устарел?\n", - " При появлении в оригинальном репозитории новых коммитов GitHub информирует вас при помощи сообщения: This branch is 5 commits behind progit:master\n", + " - Появится сообщение: This branch is N commits behind progit:master\n", "\n", "6.1 Как обновить форк?\n", - " Sync fork - Update branch\n", + " - Sync fork - Update branch\n", "\n", - "Добавление участников\n", + "### Добавление участников\n", "7 Как добавить участников в ваш репозиторий, чтобы команда могла работать над одним репозиторием? (Рисунок 112)\n", - " «Settings» - «Collaborators» - «Add collaborator»\n", + " - Settings - Collaborators - Add collaborator\n", "\n", - "Упоминания и уведомления\n", + "### Упоминания и уведомления\n", "8 Какой символ нужен для упоминания кого-либо? (Рисунок 118)\n", - " Символ \"@\"\n", + " - Символ \"@\"\n", + "\n", "8.1 Где находится Центр уведомлений, напишите ссылку (Рисунок 121)\n", - " https://github.com/notifications\n", + " - https://github.com/notifications\n", "\n", - "Особенные файлы\n", + "### Особенные файлы\n", "9 Что такое и зачем нужен файл README\n", - " Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику.\n", + " - Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику\n", "\n", "9.1 Что такое и зачем нужен файл CONTRIBUTING (Рисунок 122)\n", - " Файл CONTRIBUTING — это документ в репозитории, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта. Основная цель этого файла — помочь новым участникам понять, как правильно взаимодействовать с проектом и что нужно делать для успешного добавления изменений.\n", - " Он описывает процесс участия: как клонировать репозиторий, создавать ветки, вносить изменения и отправлять их на проверку через pull requests. В файле устанавливаются стандарты и требования по коду, формату коммитов, стилю программирования, тестированию и другим аспектам разработки.\n", + " - Файл CONTRIBUTING — это документ, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта\n", + "\n", "\n", - "Управление проектом\n", + "### Управление проектом\n", "10 Как изменить основную ветку (Рисунок 123)\n", - " «Options» - Default branch\n", + " - Settings - Default branch\n", + "\n", "10.1 Как передать проект? какая кнопка? (рисунок 124)\n", - " «Options» - «Transfer ownership» \n", + " - Settings - Transfer ownership\n", + "\n", "10.2 Что такое файл .gitignore?\n", - " Файл .gitignore — это текстовый файл в системе контроля версий Git, который содержит список файлов и каталогов, которые Git должен игнорировать и не отслеживать.\n", + " - Файл .gitignore — это файл в системе контроля версий Git, который содержит список файлов и папок, которые не должны отслеживаться\n", "\n" ] } diff --git a/github/quiz.py b/github/quiz.py new file mode 100644 index 00000000..8c60003e --- /dev/null +++ b/github/quiz.py @@ -0,0 +1,136 @@ +"""[TASK] Контрибьютинг в Open Source #8.""" + +# ### GitHub +# 1.1. Что такое GitHub? +# - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions +# +# 1.2. Как GitHub связан с Git? +# - Git — это распределенная система контроля версий +# - GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями +# +# 1.3. Чем отличается fork репозитория от его клонирования (clone)? +# - Fork создает полностью независимую копию репозитория на GitHub-аккаунте +# - Clone создает локальную копию репозитория +# +# 1.4. Зачем нужны и как работают pull requests? +# - Pull request (PR) — это механизм слияния своей ветки с основной веткой репозитория для внесения изменений +# - Pull request нужен для ​code review и обсуждения изменений перед слиянием +# +# - Как работает pull request:​ +# - Разработчик создает ветку с изменениями +# - Отправляет ветку в репозиторий +# - Открывает pull request через GitHub +# - Команда обсуждает и проверяет код +# - Затем ветки сливают +# +# 1.5. GitHub использует ваш почтовый адрес для привязки ваших Git коммитов к вашей учётной записи? +# - Да +# +# 1.6 Какая команда генерирует SSH ключ для Доступа по SSH к репозиторию (Рисунок 83) +# - ssh-keygen +# +# ### Внесение собственного вклада в проекты +# 2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117) +# - Нужно сделать Pull request, указав другой Pull request +# - Это нужно для того, чтобы обеспечить правильный порядок слияния или избежать конфликтов и ошибок +# +# ### Рабочий процесс с использованием GitHub +# 3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект +# - Форкнуть проект +# - Клонировать форк локально +# - Создать отдельную ветку для изменений +# - Внести изменения +# - Закоммитить изменения +# - Отправить изменения в свой форк +# - Создать Pull Request +# - Подождать проверки +# +# 3.1 Какие практики принято соблюдать при создании Pull Request чтобы закрыть автоматический issues? +# - В описании pull request использовать ключевые слова и номер issue: +# - Fixes #номер, Closes #номер, Resolves #номер +# +# - Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues? +# - Использовать те же ключевые слова и номер issue в сообщении коммита +# +# 3.2 Как отклонить/закрыть пул реквест? (предоставьте скриншот где это в гитхабе) +# ![image.png](attachment:image.png) +# +# 3.3 Перед отправкой пул реквеста нужно ли создавать ишьюс? +# - Нет, это не обязательно, но зависит от практик конкретного проекта +# +# 3.4 В какой вкладке можно посмотреть список изменений который был в пул реквесте? (Рисунок 92) +# - Files changed +# +# 3.5 В какой вкладке находится страница обсуждений пул реквеста? (Рисунок 94) +# - Conversation +# +# ### Создание запроса на слияние +# 4 Можно ли открыть пул реквест, если вы ничего не вносили в FORK? +# - Нет +# +# 4.1 Что нужно сделать чтобы открыть пул реквест? (Рисунок 90) +# - Сделать форк репозитория и склонировать его +# - Создать новую ветку в репозитории для изменений +# - Внести изменения и закоммитить изменения в этой ветке +# - Отправить (push) ветку с изменениями в форк на GitHub +# - На GitHub после пуша появится нажать "Compare & pull request" +# - Создать пул реквест "Create pull request" +# +# 4.2 Что нужно сделать Если ваш Форк устарел? +# - Обновить форк +# - Добавить исходный репозиторий как удалённый с именем «upstream» +# - Получить (fetch) все изменения из оригинального репозитория +# - Переключиться на основную ветку +# - Синхронизировать изменения merge или rebase +# - Отправить (push) изменения в свой форк +# +# 4.3 Что нужно сделать если в пул реквесте имеются конфликты слияния (Рисунок 96) +# - Разрешить конфликты и запушить исправленную версию +# +# ### Отрывки кода +# 5 Что нужно сделать Для добавления отрывка кода в комментарии к ишьюсу? (Рисунок 104) +# - Отметить его обратными кавычками +# +# 5.1 На какую клавишу нажать клавишу чтобы выделенный текст был включён как цитата в ваш комментарий?(Рисунок 105) +# - Клавиша "r" или символ ">" +# +# 5.2 Как вставить картинку в ишьюс? (Рисунок 108) +# - Перетащить картинку или скопировать изображение +# +# ### Поддержание GitHub репозитория в актуальном состоянии +# 6 Как понять что ваш форк устарел? +# - Появится сообщение: This branch is N commits behind progit:master +# +# 6.1 Как обновить форк? +# - Sync fork - Update branch +# +# ### Добавление участников +# 7 Как добавить участников в ваш репозиторий, чтобы команда могла работать над одним репозиторием? (Рисунок 112) +# - Settings - Collaborators - Add collaborator +# +# ### Упоминания и уведомления +# 8 Какой символ нужен для упоминания кого-либо? (Рисунок 118) +# - Символ "@" +# +# 8.1 Где находится Центр уведомлений, напишите ссылку (Рисунок 121) +# - https://github.com/notifications +# +# ### Особенные файлы +# 9 Что такое и зачем нужен файл README +# - Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику +# +# 9.1 Что такое и зачем нужен файл CONTRIBUTING (Рисунок 122) +# - Файл CONTRIBUTING — это документ, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта +# +# +# ### Управление проектом +# 10 Как изменить основную ветку (Рисунок 123) +# - Settings - Default branch +# +# 10.1 Как передать проект? какая кнопка? (рисунок 124) +# - Settings - Transfer ownership +# +# 10.2 Что такое файл .gitignore? +# - Файл .gitignore — это файл в системе контроля версий Git, который содержит список файлов и папок, которые не должны отслеживаться +# +# From 9a6fcdd703496e811a571b7be56fa1009fbff22c Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sat, 15 Nov 2025 00:08:33 +0300 Subject: [PATCH 19/37] Update repository link in opensource.py Signed-off-by: Raushan Nigmatullin --- github/opensource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/opensource.py b/github/opensource.py index 88e7d983..71140213 100644 --- a/github/opensource.py +++ b/github/opensource.py @@ -5,7 +5,7 @@ # Есть ли у него лицензия? Обычно в корне репозитория находится файл LICENSE. # - Да # Напишите название понравившейся компании и ссылку на репозиторий -# - https://github.com/yandex +# - https://github.com/Shailaja-poojari/hacktoberfest-2025 # Проект активно принимает стороннюю помощь? # - Да # Напишите второе улучшение которое вы сделали From 2e5e7226e8db48cb30a092d110ab6e098c4e9990 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sat, 15 Nov 2025 16:21:14 +0300 Subject: [PATCH 20/37] Add links to Data Science project resources Signed-off-by: Raushan Nigmatullin --- github/quiz.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/github/quiz.py b/github/quiz.py index 8c60003e..3957c096 100644 --- a/github/quiz.py +++ b/github/quiz.py @@ -30,6 +30,18 @@ # - ssh-keygen # # ### Внесение собственного вклада в проекты +# 2.1 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV +# +# 2.2 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/tree/dev +# +# 2.4 480 +# +# 2.6 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/pull/1 +# +# 2.7 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/pulls?q=is%3Apr+is%3Aclosed +# +# 2.8 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/tree/af076f522bbf6f2fa41b67df99302a55a779bea6 +# # 2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117) # - Нужно сделать Pull request, указав другой Pull request # - Это нужно для того, чтобы обеспечить правильный порядок слияния или избежать конфликтов и ошибок From 39757bb55ed6c856ce8256551ac4344cae594deb Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sat, 15 Nov 2025 16:27:45 +0300 Subject: [PATCH 21/37] Fix typo and add PR reference in opensource.py Signed-off-by: Raushan Nigmatullin --- github/opensource.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/github/opensource.py b/github/opensource.py index 71140213..24356a7d 100644 --- a/github/opensource.py +++ b/github/opensource.py @@ -10,6 +10,7 @@ # - Да # Напишите второе улучшение которое вы сделали # - Исправил опечатку в коде +# - Два изменения в одном PR: Shailaja-poojari/hacktoberfest-2025#48 # Посмотрите на коммиты в основной ветке, напишите общее количество # - 27 # Когда был последний коммит? @@ -38,3 +39,4 @@ # - Да # Как давно были объединены пул-реквесты? (На странице Pull Request GitHub-репозитория щелкните на вкладку «Closed», чтобы увидеть закрытые пул-реквесты.) # - 12 часов +# Firstcontributions: firstcontributions/first-contributions#107001 From 2c44f6064a49a0f20b7e53778a87b6a260ef8be7 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Fri, 12 Dec 2025 20:12:53 +0300 Subject: [PATCH 22/37] [TASK] Python #1 (https://github.com/SENATOROVAI/python/issues/1) Closes https://github.com/SENATOROVAI/python/issues/1 --- ...pter_4_choosing_understandable_names.ipynb | 195 ++++++++++++++++++ python/makarov/lesson01_variables.py | 102 +++++++++ 2 files changed, 297 insertions(+) create mode 100644 python/clean-code/chapter_4_choosing_understandable_names.ipynb create mode 100644 python/makarov/lesson01_variables.py diff --git a/python/clean-code/chapter_4_choosing_understandable_names.ipynb b/python/clean-code/chapter_4_choosing_understandable_names.ipynb new file mode 100644 index 00000000..92b891ca --- /dev/null +++ b/python/clean-code/chapter_4_choosing_understandable_names.ipynb @@ -0,0 +1,195 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "1922215f", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Глава 4. Выбор понятных имен.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "29a17992", + "metadata": {}, + "source": [ + "### Схемы регистра имен\n", + "\n", + "Змеиный регистр (snake_case) - разделяет слова символом подчеркивания. Константы часто записываются\n", + "в верхнем змеином регистре (UPPER_SNAKE_CASE).\n", + "\n", + "Верблюжий регистр (camelCase) — слова записываются в нижнем регистре,\n", + "но второе и следующие слова начинаются с заглавной. \n", + "\n", + "Схема Pascal (PascalCase) — названа так, потому что применяется в языке\n", + "программирования Pascal; аналогична схеме верблюжьего регистра, но первое\n", + "слово в ней тоже начинается с заглавной.\n", + "\n", + "На практике чаще всего встречаются змеиный и верблюжий регистры. " + ] + }, + { + "cell_type": "markdown", + "id": "b12729f7", + "metadata": {}, + "source": [ + "### Соглашения об именах PEP 8\n", + "\n", + "- Все буквы должны быть буквами ASCII — то есть латинскими буквами\n", + "верхнего и нижнего регистров без диакритических знаков.\n", + "\n", + "- Имена модулей должны быть короткими и состоять только из букв нижнего\n", + "регистра.\n", + "\n", + "- Имена классов необходимо записывать в схеме Pascal.\n", + "\n", + "- Имена констант следует записывать в верхнем змеином регистре.\n", + "\n", + "- Имена функций, методов и переменных записывают в нижнем змеином\n", + "регистре.\n", + "\n", + "- Первый аргумент методов всегда должен называться self в нижнем регистре.\n", + "\n", + "- Первый аргумент методов классов всегда должен называться cls в нижнем\n", + "регистре.\n", + "\n", + "- Приватные атрибуты классов всегда начинают с символа подчеркивания ( _ ).\n", + "\n", + "- Публичные атрибуты классов никогда не начинают с символа подчеркивания ( _ )." + ] + }, + { + "cell_type": "markdown", + "id": "59a1cf3e", + "metadata": {}, + "source": [ + "### Длина имен\n", + "\n", + "Очевидно, имена не должны быть слишком короткими или слишком длинными. \n", + "Но так как код читают чаще, чем пишут, лучше все-таки задавать более длинные имена переменных.\n", + "\n", + "1. Слишком короткие имена:\n", + "- Вы можете забыть их точный смысл через несколько дней или недель. \n", + "\n", + "- Сокращения и одно-двухбуквенные имена легко записать, но они плохо читаются.\n", + "\n", + "- Сокращенные имена вида mon — могут означать monitor, month, monster и множество других слов.\n", + "\n", + "- Имя из одного слова — например, start (начало) — может трактоваться поразному: начало чего? При отсутствии уточнения другие люди вас вряд ли\n", + "поймут.\n", + "\n", + "- В отдельных случаях короткие имена переменных вполне допустимы. Например, имя i часто используется с переменными циклов for, перебирающих диапазоны чисел или индексов списка, а j и k (следующие за i в алфавитном порядке) используются с вложенными циклами.\n", + "\n", + "- Еще одно исключение — использование x и y для декартовых координат. \n", + "\n", + "\n", + "2. Слишком длинные имена:\n", + "- Как правило, чем больше область видимости имени, тем более содержательным оно должно быть. \n", + "\n", + "- Лучше использовать более содержательное имя, например, salesClientMonthlyPayment или annual_electric_bill_payment. Дополнительные слова в имени уточняют смысл и устраняют неоднозначность.\n", + "\n", + "- Не пропускайте буквы в своем коде.\n", + "\n", + "- Кроме того, старайтесь использовать короткие фразы, с которыми ваш код читается как обычный текст. Например, имя number_of_trials читается лучше, чем number_trials.\n", + "\n", + "\n", + "3. Префиксы в именах:\n", + "- Префиксы в именах иногда избыточны. Например, если у вас есть класс Cat с атрибутом weight, очевидно, что weight (вес) относится к кошке (cat). Таким образом, имя catWeight будет слишком подробным и длинным.\n", + "\n", + "- С другой стороны, префиксы is и has у переменных, содержащих логические значения, или функций и методов, возвращающих логические значения, делают эти имена более понятными. \n", + "\n", + "- Также включение единиц измерения в имена может предоставить полезную информацию.\n", + "\n", + "4. Последовательные числовые суффиксы в именах:\n", + "- Последовательные числовые суффиксы в именах указывают на то, что вам, возможно, стоит изменить тип данных переменной или включить дополнительную информацию в имя.\n", + "\n", + "- Числа сами по себе, как правило, не предоставляют достаточной информации, чтобы имена можно было отличить друг от друга.\n", + "\n", + "- Имена переменных вида payment1, payment2 и payment3 не сообщают читателю кода, чем они различаются. Возможно, программисту стоит преобразовать эти три переменные в один список или переменную-кортеж с именем payments, в которой хранятся три значения.\n" + ] + }, + { + "cell_type": "markdown", + "id": "5932b644", + "metadata": {}, + "source": [ + "### Выбирайте имена, пригодные для поиска\n", + "\n", + "- Во всех программах, кроме самых коротких, вам, вероятно, придется воспользоваться редактором или функцией поиска (Ctrl-F) в IDE, чтобы найти упоминания переменных и функций. \n", + "\n", + "- Если вы будете выбирать короткие, обобщенные имена переменных (например, num или a), вы наверняка получите ряд ложных совпадений. \n", + "\n", + "- Чтобы имя было найдено немедленно, создавайте уникальные имена с более длинными именами переменных, которые содержат конкретную информацию.\n", + "\n", + "- Имя email слишком многозначно, поэтому лучше выбрать более содержательное имя: emailAddress, downloadEmailAttachment, emailMessage, replyToAddress и т. д. Такое имя не только более точное, но его проще найти в исходном коде." + ] + }, + { + "cell_type": "markdown", + "id": "80877125", + "metadata": {}, + "source": [ + "### Избегайте шуток, каламбуров и культурных отсылок\n", + "\n", + "- При выборе имен в программе у вас может возникнуть соблазн использовать шутки, каламбуры или культурные отсылки, чтобы ваш код выглядел более непринужденно. Не делайте этого. \n", + "\n", + "- Лучше всего писать код, понятный тем, для кого английский язык не является родным, то есть прямолинейно, традиционно и без юмора. " + ] + }, + { + "cell_type": "markdown", + "id": "779d8416", + "metadata": {}, + "source": [ + "### Не заменяйте встроенные имена\n", + "\n", + "- Никогда не используйте встроенные имена Python для своих переменных. Например, присвоив переменной имя list или set, вы заместите функции Python list() и set(), что позднее может привести к появлению ошибок. \n", + "\n", + "- Другая распространенная проблема — присваивание файлам .py имен, совпадающих с именами сторонних модулей. " + ] + }, + { + "cell_type": "markdown", + "id": "dddb1e48", + "metadata": {}, + "source": [ + "### Худшие из возможных имен\n", + "\n", + "- Имя data — ужасное и абсолютно бессодержательное, потому что буквально любая переменная содержит данные (data). \n", + "\n", + "- То же можно сказать об имени var — все равно что выбрать для собаки кличку Собака. " + ] + }, + { + "cell_type": "markdown", + "id": "619669b7", + "metadata": {}, + "source": [ + "### Итоги\n", + "\n", + "- Имена не должны быть слишком короткими или слишком длинными. \n", + "\n", + "- Однако часто лучше сделать имя избыточным, чем недостаточно содержательным.\n", + "\n", + "- Имя должно быть лаконичным, но информативным. \n", + "\n", + "- Оно должно легко находиться функцией поиска Ctrl-F.\n", + "\n", + "- Выбирайте имена прямолинейные, традиционные и без отсылок к хохмам. \n", + "\n", + "- Избегайте имен, уже используемых стандартной библиотекой Python\n", + "\n", + "- Использование понятных имен — основополагающий фактор разработки качественного программного обеспечения." + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/lesson01_variables.py b/python/makarov/lesson01_variables.py new file mode 100644 index 00000000..eb3e9170 --- /dev/null +++ b/python/makarov/lesson01_variables.py @@ -0,0 +1,102 @@ +"""Переменные.""" + +# # Как объявить переменную в Питоне + +# + +# Создание или объявление переменной в Питоне происходит +# в тот момент, когда вы присваиваете ей определенное значение + +x_var = 42 +print(x_var) +# - + +# Кроме того, переменной можно задать строковое (текстовое) значение. +y_var = "Just a string" +print(y_var) + +# + +# В Питоне можно записать значения сразу в +# несколько переменных или присвоить одно и +# то же значение нескольким переменным. + +a_var, b_var, c_var = 1, 2, 3 +print(a_var, b_var, c_var) + +# + +# Или задать одно и то же значение нескольким переменным + +a_var = b_var = c_var = 10 +print(a_var, b_var, c_var) + +# + +# Список можно «распаковать» (unpack) в несколько переменных: + +numbers = [1, 2, 3] +x1_var, y1_var, z1_var = numbers +print(x1_var, y1_var, z1_var) + + +a_var = 5 # целое число (int) +float_var = 3.14 # число с плавающей точкой (float) +str_var = "Hello" # строка (str) +d_var = True # булево значение (bool) +print(type(a_var)) # +print(type(b_var)) # +print(type(c_var)) # +print(type(d_var)) # +# - + +# Присвоение и преобразование типа данных +e_var = 100 # целое число +print(type(e_var)) # +f_var = float(e_var) # преобразование в число с плавающей точкой +print(type(f_var)) # +g_var = str(e_var) # преобразование в строку +print(type(g_var)) # + +# преобразуем дробь в целочисленное значение +# обратите внимание, что округления в большую сторону не происходит +round_var = float(9.99) +h_var = int(round_var) +print(round_var, h_var, sep=", ") + +# # Именование переменных + +# Имя переменной может включать только латинские буквы и цифры, а также +# символ подчеркивания + +# Одновременно оно не должно начинаться с цифры. Питон отличает заглавную +# от строчной буквы. +# +# Пробелы и кириллицу использовать нельзя. + +# my-variable = 'Так делать нельзя' +# +# 123variable = 'Так делать нельзя' +# +# my variable = 'Так делать нельзя' + +# + +# Как можно преобразовать список чисел таким образом, +# чтобы каждый элемент списка превратился в отдельную строку? + +# вариант 1: объявить новый список и в цикле for помещать туда +# строковые значения + +list_str = [] +nums = [1, 2, 3, 4, 5] + +for x_var in nums: + list_str.append(str(x_var)) + +print(list_str) + +# вариант 2: использовать list comprehension +list_str_comp = [str(x_var) for x_var in nums] + +print(list_str_comp) + +# вариант 3: функции map() и list() +list_str_map = list(map(str, nums)) + +print(list_str_map) From fe38c1fe19bf25c108f3061cb35d52d9a98c2a77 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Tue, 16 Dec 2025 13:33:38 +0300 Subject: [PATCH 23/37] [TASK] Python #1 (https://github.com/SENATOROVAI/python/issues/1) Closes https://github.com/SENATOROVAI/python/issues/1 --- ...chapter_4_choosing_understandable_names.py | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 python/clean-code/chapter_4_choosing_understandable_names.py diff --git a/python/clean-code/chapter_4_choosing_understandable_names.py b/python/clean-code/chapter_4_choosing_understandable_names.py new file mode 100644 index 00000000..7c75b412 --- /dev/null +++ b/python/clean-code/chapter_4_choosing_understandable_names.py @@ -0,0 +1,171 @@ +"""Глава 4. + +Выбор понятных имен. +""" + +# ### Схемы регистра имен +# +# Змеиный регистр (snake_case) - разделяет слова символом подчеркивания. +# Константы часто записываются +# в верхнем змеином регистре (UPPER_SNAKE_CASE). +# +# Верблюжий регистр (camelCase) — слова записываются в нижнем регистре, +# но второе и следующие слова начинаются с заглавной. +# +# Схема Pascal (PascalCase) — названа так, потому что применяется в языке +# программирования Pascal; аналогична схеме верблюжьего регистра, но первое +# слово в ней тоже начинается с заглавной. +# +# На практике чаще всего встречаются змеиный и верблюжий регистры. + +# ### Соглашения об именах PEP 8 +# +# - Все буквы должны быть буквами ASCII — то есть латинскими буквами +# верхнего и нижнего регистров без диакритических знаков. +# +# - Имена модулей должны быть короткими и состоять только из букв нижнего +# регистра. +# +# - Имена классов необходимо записывать в схеме Pascal. +# +# - Имена констант следует записывать в верхнем змеином регистре. +# +# - Имена функций, методов и переменных записывают в нижнем змеином +# регистре. +# +# - Первый аргумент методов всегда должен называться self в нижнем регистре. +# +# - Первый аргумент методов классов всегда должен называться cls в нижнем +# регистре. +# +# - Приватные атрибуты классов всегда начинают с символа подчеркивания ( _ ). +# +# - Публичные атрибуты классов никогда не начинают с символа +# подчеркивания ( _ ). + +# ### Длина имен +# +# Очевидно, имена не должны быть слишком короткими или слишком длинными. +# Но так как код читают чаще, чем пишут, лучше все-таки задавать +# более длинные имена переменных. +# +# 1. Слишком короткие имена: +# - Вы можете забыть их точный смысл через несколько дней или недель. +# +# - Сокращения и одно-двухбуквенные имена легко записать, +# но они плохо читаются. +# +# - Сокращенные имена вида mon — могут означать monitor, month, +# monster и множество других слов. +# +# - Имя из одного слова — например, start (начало) — может +# трактоваться по-разному: начало чего? При отсутствии +# уточнения другие люди вас вряд ли поймут. +# +# - В отдельных случаях короткие имена переменных вполне допустимы. +# Например, имя i часто используется с переменными циклов for, +# перебирающих диапазоны чисел или индексов списка, а j и k +# (следующие за i в алфавитном порядке) используются с вложенными циклами. +# +# - Еще одно исключение — использование x и y для декартовых координат. +# +# +# 2. Слишком длинные имена: +# - Как правило, чем больше область видимости имени, тем более +# содержательным оно должно быть. +# +# - Лучше использовать более содержательное имя, например +# salesClientMonthlyPayment или annual_electric_bill_payment. +# Дополнительные слова в имени уточняют смысл и устраняют неоднозначность. +# +# - Не пропускайте буквы в своем коде. +# +# - Кроме того, старайтесь использовать короткие фразы, с которыми ваш +# код читается как обычный текст. Например, имя number_of_trials +# читается лучше, чем number_trials. +# +# +# 3. Префиксы в именах: +# - Префиксы в именах иногда избыточны. Например, если у вас есть класс +# Cat с атрибутом weight, очевидно, что weight (вес) относится к кошке +# (cat). Таким образом, имя catWeight будет слишком подробным и длинным. +# +# - С другой стороны, префиксы is и has у переменных, содержащих логические +# значения, или функций и методов, возвращающих логические значения, +# делают эти имена более понятными. +# +# - Также включение единиц измерения в имена может предоставить +# полезную информацию. +# +# 4. Последовательные числовые суффиксы в именах: +# - Последовательные числовые суффиксы в именах указывают на то, что вам, +# возможно, стоит изменить тип данных переменной или включить дополнительную +# информацию в имя. +# +# - Числа сами по себе, как правило, не предоставляют достаточной информации, +# чтобы имена можно было отличить друг от друга. +# +# - Имена переменных вида payment1, payment2 и payment3 не сообщают читателю +# кода, чем они различаются. Возможно, программисту стоит преобразовать эти +# три переменные в один список или переменную-кортеж с именем payments, в +# которой хранятся три значения. +# + +# ### Выбирайте имена, пригодные для поиска +# +# - Во всех программах, кроме самых коротких, вам, вероятно, придется +# воспользоваться редактором или функцией поиска (Ctrl-F) в IDE, чтобы найти +# упоминания переменных и функций. +# +# - Если вы будете выбирать короткие, обобщенные имена переменных (например, +# num или a), вы наверняка получите ряд ложных совпадений. +# +# - Чтобы имя было найдено немедленно, создавайте уникальные имена с более +# длинными именами переменных, которые содержат конкретную информацию. +# +# - Имя email слишком многозначно, поэтому лучше выбрать более содержательное +# имя: emailAddress, downloadEmailAttachment, emailMessage, replyToAddress и +# т. д. Такое имя не только более точное, но его проще найти в исходном коде. + +# ### Избегайте шуток, каламбуров и культурных отсылок +# +# - При выборе имен в программе у вас может возникнуть соблазн использовать +# шутки, каламбуры или культурные отсылки, чтобы ваш код выглядел более +# непринужденно. Не делайте этого. +# +# - Лучше всего писать код, понятный тем, для кого английский язык не является +# родным, то есть прямолинейно, традиционно и без юмора. + +# ### Не заменяйте встроенные имена +# +# - Никогда не используйте встроенные имена Python для своих переменных. +# Например, присвоив переменной имя list или set, вы заместите функции Python +# list() и set(), что позднее может привести к появлению ошибок. +# +# - Другая распространенная проблема — присваивание файлам .py имен, +# совпадающих с именами сторонних модулей. + +# ### Худшие из возможных имен +# +# - Имя data — ужасное и абсолютно бессодержательное, потому что буквально +# любая переменная содержит данные (data). +# +# - То же можно сказать об имени var — все равно что выбрать для собаки кличку +# Собака. + +# ### Итоги +# +# - Имена не должны быть слишком короткими или слишком длинными. +# +# - Однако часто лучше сделать имя избыточным, чем недостаточно содержательным. +# +# - Имя должно быть лаконичным, но информативным. +# +# - Оно должно легко находиться функцией поиска Ctrl-F. +# +# - Выбирайте имена прямолинейные, традиционные и без отсылок к хохмам. +# +# - Избегайте имен, уже используемых стандартной библиотекой Python +# +# - Использование понятных имен — основополагающий фактор разработки +# качественного программного обеспечения. From 61f12577e3d49a1da72ed4494841c6efd6b574fc Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Tue, 16 Dec 2025 14:55:16 +0300 Subject: [PATCH 24/37] temp temp --- python/makarov/chapter_1_variables.ipynb | 199 +++++++++++++++++++++++ python/makarov/chapter_1_variables.py | 90 ++++++++++ 2 files changed, 289 insertions(+) create mode 100644 python/makarov/chapter_1_variables.ipynb create mode 100644 python/makarov/chapter_1_variables.py diff --git a/python/makarov/chapter_1_variables.ipynb b/python/makarov/chapter_1_variables.ipynb new file mode 100644 index 00000000..a88e9db4 --- /dev/null +++ b/python/makarov/chapter_1_variables.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0878077e", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Переменные.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "e7dfc7f5", + "metadata": {}, + "source": [ + "# Как объявить переменную в Питоне" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46722efc", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание или объявление переменной в Питоне происходит\n", + "# в тот момент, когда вы присваиваете ей определенное значение\n", + "\n", + "x_var = 42\n", + "print(x_var)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c95f60aa", + "metadata": {}, + "outputs": [], + "source": [ + "# Кроме того, переменной можно задать строковое (текстовое) значение.\n", + "y_var = \"Just a string\"\n", + "print(y_var)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9fbd5ba8", + "metadata": {}, + "outputs": [], + "source": [ + "# В Питоне можно записать значения сразу в\n", + "# несколько переменных или присвоить одно и\n", + "# то же значение нескольким переменным.\n", + "\n", + "a_var, b_var, c_var = 1, 2, 3\n", + "print(a_var, b_var, c_var)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1cd2e5a7", + "metadata": {}, + "outputs": [], + "source": [ + "# Или задать одно и то же значение нескольким переменным\n", + "\n", + "a_var = b_var = c_var = 10\n", + "print(a_var, b_var, c_var)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "636ef285", + "metadata": {}, + "outputs": [], + "source": [ + "# Список можно «распаковать» (unpack) в несколько переменных:\n", + "\n", + "numbers = [1, 2, 3]\n", + "x_unpack, y_unpack, z_unpack = numbers\n", + "print(x_unpack, y_unpack, z_unpack)" + ] + }, + { + "cell_type": "markdown", + "id": "2d522ad9", + "metadata": {}, + "source": [ + "# Типы данных в питоне" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf946a3a", + "metadata": {}, + "outputs": [], + "source": [ + "# Автоматическое определение типа данных\n", + "\n", + "int_var = 5 # целое число (int)\n", + "float_var = 3.14 # число с плавающей точкой (float)\n", + "str_var = \"Hello\" # строка (str)\n", + "d_var = True # булево значение (bool)\n", + "print(type(int_var)) # \n", + "print(type(float_var)) # \n", + "print(type(str_var)) # \n", + "print(type(d_var)) # " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70c9a208", + "metadata": {}, + "outputs": [], + "source": [ + "# Присвоение и преобразование типа данных\n", + "e_var = 100 # целое число\n", + "print(type(e_var)) # \n", + "f_var = float(e_var) # преобразование в число с плавающей точкой\n", + "print(type(f_var)) # \n", + "g_var = str(e_var) # преобразование в строку\n", + "print(type(g_var)) # " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74f979de", + "metadata": {}, + "outputs": [], + "source": [ + "# преобразуем дробь в целочисленное значение\n", + "# обратите внимание, что округления в большую сторону не происходит\n", + "a_f_var = float(9.99)\n", + "h_var = int(a_f_var)\n", + "print(a_f_var, h_var, sep=\", \")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb8c5834", + "metadata": {}, + "outputs": [], + "source": [ + "# Как можно преобразовать список чисел таким образом,\n", + "# чтобы каждый элемент списка превратился в отдельную строку?\n", + "\n", + "# вариант 1: объявить новый список и в цикле for помещать туда строковые значения\n", + "\n", + "list_str = []\n", + "nums = [1, 2, 3, 4, 5]\n", + "\n", + "for x_var in nums:\n", + " list_str.append(str(x_var))\n", + "\n", + "print(list_str)\n", + "\n", + "# вариант 2: использовать list comprehension\n", + "list_str_comp = [str(x_var) for x_var in nums]\n", + "\n", + "print(list_str_comp)\n", + "\n", + "# вариант 3: функции map() и list()\n", + "list_str_map = list(map(str, nums))\n", + "\n", + "print(list_str_map)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_1_variables.py b/python/makarov/chapter_1_variables.py new file mode 100644 index 00000000..2630343c --- /dev/null +++ b/python/makarov/chapter_1_variables.py @@ -0,0 +1,90 @@ +"""Переменные.""" + +# # Как объявить переменную в Питоне + +# + +# Создание или объявление переменной в Питоне происходит +# в тот момент, когда вы присваиваете ей определенное значение + +x_var = 42 +print(x_var) +# - + +# Кроме того, переменной можно задать строковое (текстовое) значение. +y_var = "Just a string" +print(y_var) + +# + +# В Питоне можно записать значения сразу в +# несколько переменных или присвоить одно и +# то же значение нескольким переменным. + +a_var, b_var, c_var = 1, 2, 3 +print(a_var, b_var, c_var) + +# + +# Или задать одно и то же значение нескольким переменным + +a_var = b_var = c_var = 10 +print(a_var, b_var, c_var) + +# + +# Список можно «распаковать» (unpack) в несколько переменных: + +numbers = [1, 2, 3] +x_unpack, y_unpack, z_unpack = numbers +print(x_unpack, y_unpack, z_unpack) +# - + +# # Типы данных в питоне + +# + +# Автоматическое определение типа данных + +int_var = 5 # целое число (int) +float_var = 3.14 # число с плавающей точкой (float) +str_var = "Hello" # строка (str) +d_var = True # булево значение (bool) +print(type(int_var)) # +print(type(float_var)) # +print(type(str_var)) # +print(type(d_var)) # +# - + +# Присвоение и преобразование типа данных +e_var = 100 # целое число +print(type(e_var)) # +f_var = float(e_var) # преобразование в число с плавающей точкой +print(type(f_var)) # +g_var = str(e_var) # преобразование в строку +print(type(g_var)) # + +# преобразуем дробь в целочисленное значение +# обратите внимание, что округления в большую сторону не происходит +a_f_var = float(9.99) +h_var = int(a_f_var) +print(a_f_var, h_var, sep=", ") + +# + +# Как можно преобразовать список чисел таким образом, +# чтобы каждый элемент списка превратился в отдельную строку? + +# вариант 1: объявить новый список и в цикле for помещать туда строковые значения + +list_str = [] +nums = [1, 2, 3, 4, 5] + +for x_var in nums: + list_str.append(str(x_var)) + +print(list_str) + +# вариант 2: использовать list comprehension +list_str_comp = [str(x_var) for x_var in nums] + +print(list_str_comp) + +# вариант 3: функции map() и list() +list_str_map = list(map(str, nums)) + +print(list_str_map) From 7bd30a39a9df39181d77b030776b70a97a612b4c Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Tue, 16 Dec 2025 18:09:05 +0300 Subject: [PATCH 25/37] temp temp --- python/makarov/chapter_4_files.ipynb | 283 +++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 python/makarov/chapter_4_files.ipynb diff --git a/python/makarov/chapter_4_files.ipynb b/python/makarov/chapter_4_files.ipynb new file mode 100644 index 00000000..12024fbe --- /dev/null +++ b/python/makarov/chapter_4_files.ipynb @@ -0,0 +1,283 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "1dfaec02", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Работа с файлами в Google Colab.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fca91875", + "metadata": {}, + "outputs": [], + "source": [ + "# Подгрузка файлов с локального компьютера на сервер Google.\n", + "# Способ 1. Вручную через вкладку 'Файлы'\n", + "# см. материалы урока на сайте" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b6ce494", + "metadata": {}, + "outputs": [], + "source": [ + "# Способ 2. Через модуль files библиотеки google.colab\n", + "\n", + "# импортируем модуль os\n", + "import os\n", + "\n", + "# импортируем библиотеку\n", + "import pandas as pd\n", + "\n", + "# из библиотеки google.colab импортируем класс files\n", + "from google.colab import files\n", + "\n", + "# создаем объект этого класса, применяем метод .upload()\n", + "uploaded = files.upload()\n", + "# Нам будет предложено выбрать файл на жестком диске.\n", + "\n", + "# посмотрим на содержимое словаря uploaded\n", + "uploaded" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e5290be", + "metadata": {}, + "outputs": [], + "source": [ + "# Чтение файлов\n", + "# После загрузки оба файла (train.csv и test.csv)\n", + "# оказываются в сессионном хранилище в папке\n", + "# под названием /content/.\n", + "\n", + "# Просмотр содержимого в папке /content/\n", + "# Модуль os и метод .walk()\n", + "\n", + "\n", + "# выводим пути к папкам (dirpath) и наименования файлов\n", + "# (filenames) и после этого\n", + "for dirpath, _, filenames in os.walk(\"/content/\"):\n", + "\n", + " # во вложенном цикле проходимся по названиям файлов\n", + " for filename in filenames:\n", + "\n", + " # и соединяем путь до папок и входящие в эти папки файлы\n", + " # с помощью метода path.join()\n", + " print(os.path.join(dirpath, filename))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a804c874", + "metadata": {}, + "outputs": [], + "source": [ + "# Команда !ls\n", + "\n", + "# посмотрим на содержимое папки content\n", + "!ls\n", + "\n", + "# заглянем внутрь sample_data\n", + "!ls /content/sample_data/" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4feca34d", + "metadata": {}, + "outputs": [], + "source": [ + "# Чтение из переменной uploaded\n", + "\n", + "\n", + "# посмотрим на тип значений словаря uploaded\n", + "type(uploaded[\"test.csv\"])\n", + "\n", + "# bytes\n", + "\n", + "# обратимся к ключу словаря uploaded и применим метод .decode()\n", + "uploaded_str = uploaded[\"test.csv\"].decode()\n", + "\n", + "# на выходе получаем обычную строку\n", + "print(type(uploaded_str))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b087b1e6", + "metadata": {}, + "outputs": [], + "source": [ + "# Если разбить строку методом .split() по символам \\r\n", + "# (возврат к началу строки) и \\n (новая строка),\n", + "# то на выходе мы получим список.\n", + "\n", + "uploaded_list = uploaded_str.split(\"\\r\\n\")\n", + "type(uploaded_list) # list" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf7354aa", + "metadata": {}, + "outputs": [], + "source": [ + "# пройдемся по этому списку, не забыв создать индекс с\n", + "# помощью функции enumerate()\n", + "for i, line in enumerate(uploaded_list):\n", + "\n", + " # начнем выводить записи\n", + " print(line)\n", + "\n", + " # когда дойдем до четвертой строки\n", + " if i == 3:\n", + "\n", + " # прервемся\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eccf535e", + "metadata": {}, + "outputs": [], + "source": [ + "# Использование функции open() и конструкции with open()\n", + "# Функция open() возвращает объект, который используется\n", + "# для чтения и изменения файла. Откроем файл train.csv.\n", + "\n", + "\n", + "# передадим функции open() адрес файла\n", + "with open(\"/content/train.csv\", encoding=\"utf-8\") as f:\n", + " content = f.read()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63dddf8b", + "metadata": {}, + "outputs": [], + "source": [ + "# Для наших целей метод .read() не очень удобен.\n", + "# Будет лучше пройтись по файлу в цикле for.\n", + "\n", + "# снова откроем файл\n", + "with open(\"/content/train.csv\", encoding=\"utf-8\") as f1:\n", + " for i, line in enumerate(f1):\n", + " print(line.strip())\n", + " if i == 3:\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6df7546", + "metadata": {}, + "outputs": [], + "source": [ + "# Еще один способ — использовать конструкцию with open().\n", + "# В этом случае специально закрывать файл не нужно.\n", + "\n", + "# скажем Питону: \"открой файл и назови его f3\"\n", + "with open(\"/content/test.csv\", encoding=\"utf-8\") as f3:\n", + "\n", + " # \"пройдись по строкам без служебных символов\"\n", + " for i, line in enumerate(f3):\n", + " print(line.strip())\n", + "\n", + " # и \"прервись на четвертой строке\"\n", + " if i == 3:\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6d20076", + "metadata": {}, + "outputs": [], + "source": [ + "# Чтение через библиотеку Pandas\n", + "\n", + "\n", + "# применим функцию read_csv() и посмотрим на первые три записи файла train.csv\n", + "train = pd.read_csv(\"/content/train.csv\")\n", + "train.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fccaab48", + "metadata": {}, + "outputs": [], + "source": [ + "# Сохранение результата в новом файле на сервере\n", + "# Теперь, когда прогноз готов, мы можем сформировать новый файл,\n", + "# назовем его result.csv, в котором будет содержаться id\n", + "# пассажира и результат, погиб или нет.\n", + "# Приведу пример того, что мы хотим получить.\n", + "\n", + "# файл с примером можно загрузить не с локального компьютера, а из Интернета\n", + "url = \"https://www.dmitrymakarov.ru/wp-content/uploads/2021/11/titanic_example.csv\"\n", + "\n", + "# просто поместим его url в функцию read_csv()\n", + "example = pd.read_csv(url)\n", + "example.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4da41378", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим новый файл result.csv с помощью функции to_csv(),\n", + "# удалив при этом индекс\n", + "# result.to_csv(\"result.csv\", index=False)\n", + "\n", + "# файл будет сохранен в 'Сессионном хранилище' и,\n", + "# если все пройдет успешно, выведем следующий текст:\n", + "# print(\"Файл успешно сохранился в сессионное хранилище!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b10934b8", + "metadata": {}, + "outputs": [], + "source": [ + "# Скачивание обратно на жесткий диск\n", + "# После этого мы можем скачать файл на жесткий диск.\n", + "# применим метод .download() объекта files\n", + "files.download(\"/content/result.csv\")" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From a6396cdf9f590f21f06e4ea72d6370e5f0f1a207 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sat, 20 Dec 2025 16:23:02 +0300 Subject: [PATCH 26/37] [TASK] Python #1 (https://github.com/SENATOROVAI/python/issues/1) Closes https://github.com/SENATOROVAI/python/issues/1 --- ...chapter_4_choosing_understandable_names.py | 96 ++++++------------- python/makarov/chapter_1_variables.ipynb | 3 +- python/makarov/chapter_1_variables.py | 3 +- 3 files changed, 32 insertions(+), 70 deletions(-) diff --git a/python/clean-code/chapter_4_choosing_understandable_names.py b/python/clean-code/chapter_4_choosing_understandable_names.py index 7c75b412..c616db4e 100644 --- a/python/clean-code/chapter_4_choosing_understandable_names.py +++ b/python/clean-code/chapter_4_choosing_understandable_names.py @@ -5,8 +5,7 @@ # ### Схемы регистра имен # -# Змеиный регистр (snake_case) - разделяет слова символом подчеркивания. -# Константы часто записываются +# Змеиный регистр (snake_case) - разделяет слова символом подчеркивания. Константы часто записываются # в верхнем змеином регистре (UPPER_SNAKE_CASE). # # Верблюжий регистр (camelCase) — слова записываются в нижнем регистре, @@ -40,118 +39,80 @@ # # - Приватные атрибуты классов всегда начинают с символа подчеркивания ( _ ). # -# - Публичные атрибуты классов никогда не начинают с символа -# подчеркивания ( _ ). +# - Публичные атрибуты классов никогда не начинают с символа подчеркивания ( _ ). # ### Длина имен # # Очевидно, имена не должны быть слишком короткими или слишком длинными. -# Но так как код читают чаще, чем пишут, лучше все-таки задавать -# более длинные имена переменных. +# Но так как код читают чаще, чем пишут, лучше все-таки задавать более длинные имена переменных. # # 1. Слишком короткие имена: # - Вы можете забыть их точный смысл через несколько дней или недель. # -# - Сокращения и одно-двухбуквенные имена легко записать, -# но они плохо читаются. +# - Сокращения и одно-двухбуквенные имена легко записать, но они плохо читаются. # -# - Сокращенные имена вида mon — могут означать monitor, month, -# monster и множество других слов. +# - Сокращенные имена вида mon — могут означать monitor, month, monster и множество других слов. # -# - Имя из одного слова — например, start (начало) — может -# трактоваться по-разному: начало чего? При отсутствии -# уточнения другие люди вас вряд ли поймут. +# - Имя из одного слова — например, start (начало) — может трактоваться поразному: начало чего? При отсутствии уточнения другие люди вас вряд ли +# поймут. # -# - В отдельных случаях короткие имена переменных вполне допустимы. -# Например, имя i часто используется с переменными циклов for, -# перебирающих диапазоны чисел или индексов списка, а j и k -# (следующие за i в алфавитном порядке) используются с вложенными циклами. +# - В отдельных случаях короткие имена переменных вполне допустимы. Например, имя i часто используется с переменными циклов for, перебирающих диапазоны чисел или индексов списка, а j и k (следующие за i в алфавитном порядке) используются с вложенными циклами. # # - Еще одно исключение — использование x и y для декартовых координат. # # # 2. Слишком длинные имена: -# - Как правило, чем больше область видимости имени, тем более -# содержательным оно должно быть. +# - Как правило, чем больше область видимости имени, тем более содержательным оно должно быть. # -# - Лучше использовать более содержательное имя, например -# salesClientMonthlyPayment или annual_electric_bill_payment. -# Дополнительные слова в имени уточняют смысл и устраняют неоднозначность. +# - Лучше использовать более содержательное имя, например, salesClientMonthlyPayment или annual_electric_bill_payment. Дополнительные слова в имени уточняют смысл и устраняют неоднозначность. # # - Не пропускайте буквы в своем коде. # -# - Кроме того, старайтесь использовать короткие фразы, с которыми ваш -# код читается как обычный текст. Например, имя number_of_trials -# читается лучше, чем number_trials. +# - Кроме того, старайтесь использовать короткие фразы, с которыми ваш код читается как обычный текст. Например, имя number_of_trials читается лучше, чем number_trials. # # # 3. Префиксы в именах: -# - Префиксы в именах иногда избыточны. Например, если у вас есть класс -# Cat с атрибутом weight, очевидно, что weight (вес) относится к кошке -# (cat). Таким образом, имя catWeight будет слишком подробным и длинным. +# - Префиксы в именах иногда избыточны. Например, если у вас есть класс Cat с атрибутом weight, очевидно, что weight (вес) относится к кошке (cat). Таким образом, имя catWeight будет слишком подробным и длинным. # -# - С другой стороны, префиксы is и has у переменных, содержащих логические -# значения, или функций и методов, возвращающих логические значения, -# делают эти имена более понятными. +# - С другой стороны, префиксы is и has у переменных, содержащих логические значения, или функций и методов, возвращающих логические значения, делают эти имена более понятными. # -# - Также включение единиц измерения в имена может предоставить -# полезную информацию. +# - Также включение единиц измерения в имена может предоставить полезную информацию. # # 4. Последовательные числовые суффиксы в именах: -# - Последовательные числовые суффиксы в именах указывают на то, что вам, -# возможно, стоит изменить тип данных переменной или включить дополнительную -# информацию в имя. +# - Последовательные числовые суффиксы в именах указывают на то, что вам, возможно, стоит изменить тип данных переменной или включить дополнительную информацию в имя. # -# - Числа сами по себе, как правило, не предоставляют достаточной информации, -# чтобы имена можно было отличить друг от друга. +# - Числа сами по себе, как правило, не предоставляют достаточной информации, чтобы имена можно было отличить друг от друга. # -# - Имена переменных вида payment1, payment2 и payment3 не сообщают читателю -# кода, чем они различаются. Возможно, программисту стоит преобразовать эти -# три переменные в один список или переменную-кортеж с именем payments, в -# которой хранятся три значения. +# - Имена переменных вида payment1, payment2 и payment3 не сообщают читателю кода, чем они различаются. Возможно, программисту стоит преобразовать эти три переменные в один список или переменную-кортеж с именем payments, в которой хранятся три значения. # # ### Выбирайте имена, пригодные для поиска # -# - Во всех программах, кроме самых коротких, вам, вероятно, придется -# воспользоваться редактором или функцией поиска (Ctrl-F) в IDE, чтобы найти -# упоминания переменных и функций. +# - Во всех программах, кроме самых коротких, вам, вероятно, придется воспользоваться редактором или функцией поиска (Ctrl-F) в IDE, чтобы найти упоминания переменных и функций. # -# - Если вы будете выбирать короткие, обобщенные имена переменных (например, -# num или a), вы наверняка получите ряд ложных совпадений. +# - Если вы будете выбирать короткие, обобщенные имена переменных (например, num или a), вы наверняка получите ряд ложных совпадений. # -# - Чтобы имя было найдено немедленно, создавайте уникальные имена с более -# длинными именами переменных, которые содержат конкретную информацию. +# - Чтобы имя было найдено немедленно, создавайте уникальные имена с более длинными именами переменных, которые содержат конкретную информацию. # -# - Имя email слишком многозначно, поэтому лучше выбрать более содержательное -# имя: emailAddress, downloadEmailAttachment, emailMessage, replyToAddress и -# т. д. Такое имя не только более точное, но его проще найти в исходном коде. +# - Имя email слишком многозначно, поэтому лучше выбрать более содержательное имя: emailAddress, downloadEmailAttachment, emailMessage, replyToAddress и т. д. Такое имя не только более точное, но его проще найти в исходном коде. # ### Избегайте шуток, каламбуров и культурных отсылок # -# - При выборе имен в программе у вас может возникнуть соблазн использовать -# шутки, каламбуры или культурные отсылки, чтобы ваш код выглядел более -# непринужденно. Не делайте этого. +# - При выборе имен в программе у вас может возникнуть соблазн использовать шутки, каламбуры или культурные отсылки, чтобы ваш код выглядел более непринужденно. Не делайте этого. # -# - Лучше всего писать код, понятный тем, для кого английский язык не является -# родным, то есть прямолинейно, традиционно и без юмора. +# - Лучше всего писать код, понятный тем, для кого английский язык не является родным, то есть прямолинейно, традиционно и без юмора. # ### Не заменяйте встроенные имена # -# - Никогда не используйте встроенные имена Python для своих переменных. -# Например, присвоив переменной имя list или set, вы заместите функции Python -# list() и set(), что позднее может привести к появлению ошибок. +# - Никогда не используйте встроенные имена Python для своих переменных. Например, присвоив переменной имя list или set, вы заместите функции Python list() и set(), что позднее может привести к появлению ошибок. # -# - Другая распространенная проблема — присваивание файлам .py имен, -# совпадающих с именами сторонних модулей. +# - Другая распространенная проблема — присваивание файлам .py имен, совпадающих с именами сторонних модулей. # ### Худшие из возможных имен # -# - Имя data — ужасное и абсолютно бессодержательное, потому что буквально -# любая переменная содержит данные (data). +# - Имя data — ужасное и абсолютно бессодержательное, потому что буквально любая переменная содержит данные (data). # -# - То же можно сказать об имени var — все равно что выбрать для собаки кличку -# Собака. +# - То же можно сказать об имени var — все равно что выбрать для собаки кличку Собака. # ### Итоги # @@ -167,5 +128,4 @@ # # - Избегайте имен, уже используемых стандартной библиотекой Python # -# - Использование понятных имен — основополагающий фактор разработки -# качественного программного обеспечения. +# - Использование понятных имен — основополагающий фактор разработки качественного программного обеспечения. diff --git a/python/makarov/chapter_1_variables.ipynb b/python/makarov/chapter_1_variables.ipynb index a88e9db4..704f4fac 100644 --- a/python/makarov/chapter_1_variables.ipynb +++ b/python/makarov/chapter_1_variables.ipynb @@ -153,7 +153,8 @@ "# Как можно преобразовать список чисел таким образом,\n", "# чтобы каждый элемент списка превратился в отдельную строку?\n", "\n", - "# вариант 1: объявить новый список и в цикле for помещать туда строковые значения\n", + "# вариант 1: объявить новый список и в цикле\n", + "# for помещать туда строковые значения\n", "\n", "list_str = []\n", "nums = [1, 2, 3, 4, 5]\n", diff --git a/python/makarov/chapter_1_variables.py b/python/makarov/chapter_1_variables.py index 2630343c..4edb366b 100644 --- a/python/makarov/chapter_1_variables.py +++ b/python/makarov/chapter_1_variables.py @@ -69,7 +69,8 @@ # Как можно преобразовать список чисел таким образом, # чтобы каждый элемент списка превратился в отдельную строку? -# вариант 1: объявить новый список и в цикле for помещать туда строковые значения +# вариант 1: объявить новый список и в цикле +# for помещать туда строковые значения list_str = [] nums = [1, 2, 3, 4, 5] From fcc53e3070562361ff809ad7ba506264414061ff Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sat, 20 Dec 2025 18:32:47 +0300 Subject: [PATCH 27/37] Delete python/makarov/lesson01_variables.py Signed-off-by: Raushan Nigmatullin --- python/makarov/lesson01_variables.py | 102 --------------------------- 1 file changed, 102 deletions(-) delete mode 100644 python/makarov/lesson01_variables.py diff --git a/python/makarov/lesson01_variables.py b/python/makarov/lesson01_variables.py deleted file mode 100644 index eb3e9170..00000000 --- a/python/makarov/lesson01_variables.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Переменные.""" - -# # Как объявить переменную в Питоне - -# + -# Создание или объявление переменной в Питоне происходит -# в тот момент, когда вы присваиваете ей определенное значение - -x_var = 42 -print(x_var) -# - - -# Кроме того, переменной можно задать строковое (текстовое) значение. -y_var = "Just a string" -print(y_var) - -# + -# В Питоне можно записать значения сразу в -# несколько переменных или присвоить одно и -# то же значение нескольким переменным. - -a_var, b_var, c_var = 1, 2, 3 -print(a_var, b_var, c_var) - -# + -# Или задать одно и то же значение нескольким переменным - -a_var = b_var = c_var = 10 -print(a_var, b_var, c_var) - -# + -# Список можно «распаковать» (unpack) в несколько переменных: - -numbers = [1, 2, 3] -x1_var, y1_var, z1_var = numbers -print(x1_var, y1_var, z1_var) - - -a_var = 5 # целое число (int) -float_var = 3.14 # число с плавающей точкой (float) -str_var = "Hello" # строка (str) -d_var = True # булево значение (bool) -print(type(a_var)) # -print(type(b_var)) # -print(type(c_var)) # -print(type(d_var)) # -# - - -# Присвоение и преобразование типа данных -e_var = 100 # целое число -print(type(e_var)) # -f_var = float(e_var) # преобразование в число с плавающей точкой -print(type(f_var)) # -g_var = str(e_var) # преобразование в строку -print(type(g_var)) # - -# преобразуем дробь в целочисленное значение -# обратите внимание, что округления в большую сторону не происходит -round_var = float(9.99) -h_var = int(round_var) -print(round_var, h_var, sep=", ") - -# # Именование переменных - -# Имя переменной может включать только латинские буквы и цифры, а также -# символ подчеркивания - -# Одновременно оно не должно начинаться с цифры. Питон отличает заглавную -# от строчной буквы. -# -# Пробелы и кириллицу использовать нельзя. - -# my-variable = 'Так делать нельзя' -# -# 123variable = 'Так делать нельзя' -# -# my variable = 'Так делать нельзя' - -# + -# Как можно преобразовать список чисел таким образом, -# чтобы каждый элемент списка превратился в отдельную строку? - -# вариант 1: объявить новый список и в цикле for помещать туда -# строковые значения - -list_str = [] -nums = [1, 2, 3, 4, 5] - -for x_var in nums: - list_str.append(str(x_var)) - -print(list_str) - -# вариант 2: использовать list comprehension -list_str_comp = [str(x_var) for x_var in nums] - -print(list_str_comp) - -# вариант 3: функции map() и list() -list_str_map = list(map(str, nums)) - -print(list_str_map) From 01ee89a6d4ea6ee133138d5cf66d21181deb16f1 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sat, 20 Dec 2025 18:46:43 +0300 Subject: [PATCH 28/37] [TASK] Python #1 (https://github.com/SENATOROVAI/intro/issues/5) Closes https://github.com/SENATOROVAI/intro/issues/5 --- python/makarov/chapter_2_data_types.ipynb | 315 ++++++++++++++++++++++ python/makarov/chapter_2_data_types.py | 150 +++++++++++ 2 files changed, 465 insertions(+) create mode 100644 python/makarov/chapter_2_data_types.ipynb create mode 100644 python/makarov/chapter_2_data_types.py diff --git a/python/makarov/chapter_2_data_types.ipynb b/python/makarov/chapter_2_data_types.ipynb new file mode 100644 index 00000000..4ef3b436 --- /dev/null +++ b/python/makarov/chapter_2_data_types.ipynb @@ -0,0 +1,315 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "af4629ba", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Типы данных.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c4c6eb4", + "metadata": {}, + "outputs": [], + "source": [ + "# Работа с числами\n", + "a_var = 25 # целое число (int)\n", + "b_var = 2.5 # число с плавающей точкой (float)\n", + "c_var = 3 + 25j # комплексное число (complex)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f0ca8b8", + "metadata": {}, + "outputs": [], + "source": [ + "# экспоненциальная запись, 2 умножить на 10 в степени 3\n", + "d_var = 2e3 # 2000.0\n", + "print(a_var, b_var, c_var, d_var, sep=\"\\n\") # 25, 2.5, (3+25j), 2000.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "251bbea3", + "metadata": {}, + "outputs": [], + "source": [ + "# Арифметические операции\n", + "\n", + "# сложение, вычитание, умножение, деление, возведение в степень\n", + "print(2 + 2, 4 - 2, 2 * 2, 4 / 2, 2**3, sep=\"\\n\") # 4, 2, 4, 2.0, 8" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10e4056c", + "metadata": {}, + "outputs": [], + "source": [ + "# новая для нас операция: разделим 7 на 2, и найдем целую часть и остаток\n", + "\n", + "# целая часть\n", + "print(7 // 2)\n", + "\n", + "# остаток от деления\n", + "print(7 % 2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7b23c9c", + "metadata": {}, + "outputs": [], + "source": [ + "# Операторы сравнения\n", + "# print(5 > 3) # больше\n", + "# print(5 < 3) # меньше\n", + "# print(5 == 5) # равно\n", + "# print(5 != 3) # не равно\n", + "# print(5 >= 5) # больше или равно\n", + "# print(5 <= 3) # меньше или равно\n", + "# print(not (4 == 4)) # отрицание" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "319053ff", + "metadata": {}, + "outputs": [], + "source": [ + "# Логические операции\n", + "\n", + "# логическое И, обе операции должны быть истинны\n", + "# print(4 > 2 and 2 != 3) # True\n", + "\n", + "# # логическое ИЛИ, хотя бы одна из операций должна быть истинна\n", + "# print(4 < 2 or 2 == 2) # True\n", + "\n", + "# # логическое НЕ, перевод истинного значения в ложное и наоборот\n", + "# print(not (4 == 4)) # False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02229c38", + "metadata": {}, + "outputs": [], + "source": [ + "# Перевод чисел в другую систему счисления\n", + "\n", + "# создадим число в десятичной системе\n", + "num = 42\n", + "# переведем в двоичную систему\n", + "print(bin(num)) # 0b101010\n", + "# переведем в восьмеричную систему\n", + "print(oct(num)) # 0o52\n", + "# переведем в шестнадцатеричную систему\n", + "print(hex(num)) # 0x2a\n", + "# и обратно в десятичную\n", + "print(int(\"0b101010\", 2)) # 42\n", + "print(int(\"0o52\", 8)) # 42\n", + "print(int(\"0x2a\", 16)) # 42" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "560f0d01", + "metadata": {}, + "outputs": [], + "source": [ + "# Строки и их операции\n", + "string_1 = \"это строка\"\n", + "string_2 = \"это тоже строка\"\n", + "print(string_1)\n", + "print(string_2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87566985", + "metadata": {}, + "outputs": [], + "source": [ + "multi_string = \"\"\"Мы все учились понемногу\n", + "Чему-нибудь и как-нибудь,\n", + "Так воспитаньем, слава богу,\n", + "У нас немудрено блеснуть.\"\"\"\n", + "print(multi_string)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07b21f0d", + "metadata": {}, + "outputs": [], + "source": [ + "# Длина строки\n", + "print(len(string_1)) # 11\n", + "print(len(multi_string)) # ninety-something" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41de8a21", + "metadata": {}, + "outputs": [], + "source": [ + "# Объединение строк\n", + "string_3 = string_1 + \" \" + string_2\n", + "print(string_3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00f13b71", + "metadata": {}, + "outputs": [], + "source": [ + "# Индекс символа в строке\n", + "\n", + "# выведем первый элемент строки multi_string\n", + "print(multi_string[0])\n", + "\n", + "# теперь выведем последний элемент\n", + "print(multi_string[-1])\n", + "\n", + "# выберем элементы с четвертого по шестой\n", + "print(multi_string[3:6])\n", + "\n", + "# выведем все элементы вплоть до второго\n", + "print(multi_string[:2])\n", + "\n", + "# а также все элементы, начиная с четвертого\n", + "print(multi_string[3:])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50357b55", + "metadata": {}, + "outputs": [], + "source": [ + "# Циклы в строках\n", + "\n", + "# выведем буквы в слове Питон\n", + "for i in \"Питон\":\n", + " print(i)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d770b0ce", + "metadata": {}, + "outputs": [], + "source": [ + "# Методы .strip() и .split()\n", + "\n", + "text = \" Пример строки для методов strip и split. \"\n", + "print(text.strip()) # убирает пробелы в начале и конце строки\n", + "print(text.split()) # разбивает строку на слова по пробелам" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b76283d3", + "metadata": {}, + "outputs": [], + "source": [ + "# посчитаем количество слов в тексте (длину списка)\n", + "len(text.split())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fce11486", + "metadata": {}, + "outputs": [], + "source": [ + "# Замена символа в строке\n", + "\n", + "# предположим данные содержатся вот в таком формате\n", + "data = \"20,25\"\n", + "\n", + "# теперь заменим ',' на '.'\n", + "data = data.replace(\",\", \".\")\n", + "\n", + "# и преобразуем в число\n", + "data_float = float(data)\n", + "print(data_float)\n", + "print(type(data_float))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e87cbf94", + "metadata": {}, + "outputs": [], + "source": [ + "# Логические значения\n", + "# создадим переменную и запишем в нее логическое значение True\n", + "# (обязательно с большой буквы)\n", + "bool_var = False\n", + "type(bool_var)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b70d875", + "metadata": {}, + "outputs": [], + "source": [ + "# напишем небольшую программу, которая будет показывать,\n", + "# какое значение содержится в переменной var\n", + "\n", + "if bool_var is True:\n", + " print(\"Значение переменной истинно\")\n", + "else:\n", + " print(\"Значение переменной ложно\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_2_data_types.py b/python/makarov/chapter_2_data_types.py new file mode 100644 index 00000000..dbdeacec --- /dev/null +++ b/python/makarov/chapter_2_data_types.py @@ -0,0 +1,150 @@ +"""Типы данных.""" + +# Работа с числами +a_var = 25 # целое число (int) +b_var = 2.5 # число с плавающей точкой (float) +c_var = 3 + 25j # комплексное число (complex) + +# экспоненциальная запись, 2 умножить на 10 в степени 3 +d_var = 2e3 # 2000.0 +print(a_var, b_var, c_var, d_var, sep="\n") # 25, 2.5, (3+25j), 2000.0 + +# + +# Арифметические операции + +# сложение, вычитание, умножение, деление, возведение в степень +print(2 + 2, 4 - 2, 2 * 2, 4 / 2, 2**3, sep="\n") # 4, 2, 4, 2.0, 8 + +# + +# новая для нас операция: разделим 7 на 2, и найдем целую часть и остаток + +# целая часть +print(7 // 2) + +# остаток от деления +print(7 % 2) + +# + +# Операторы сравнения +# print(5 > 3) # больше +# print(5 < 3) # меньше +# print(5 == 5) # равно +# print(5 != 3) # не равно +# print(5 >= 5) # больше или равно +# print(5 <= 3) # меньше или равно +# print(not (4 == 4)) # отрицание + +# + +# Логические операции + +# логическое И, обе операции должны быть истинны +# print(4 > 2 and 2 != 3) # True + +# # логическое ИЛИ, хотя бы одна из операций должна быть истинна +# print(4 < 2 or 2 == 2) # True + +# # логическое НЕ, перевод истинного значения в ложное и наоборот +# print(not (4 == 4)) # False + +# + +# Перевод чисел в другую систему счисления + +# создадим число в десятичной системе +num = 42 +# переведем в двоичную систему +print(bin(num)) # 0b101010 +# переведем в восьмеричную систему +print(oct(num)) # 0o52 +# переведем в шестнадцатеричную систему +print(hex(num)) # 0x2a +# и обратно в десятичную +print(int("0b101010", 2)) # 42 +print(int("0o52", 8)) # 42 +print(int("0x2a", 16)) # 42 +# - + +# Строки и их операции +string_1 = "это строка" +string_2 = "это тоже строка" +print(string_1) +print(string_2) + +multi_string = """Мы все учились понемногу +Чему-нибудь и как-нибудь, +Так воспитаньем, слава богу, +У нас немудрено блеснуть.""" +print(multi_string) + +# Длина строки +print(len(string_1)) # 11 +print(len(multi_string)) # ninety-something + +# Объединение строк +string_3 = string_1 + " " + string_2 +print(string_3) + +# + +# Индекс символа в строке + +# выведем первый элемент строки multi_string +print(multi_string[0]) + +# теперь выведем последний элемент +print(multi_string[-1]) + +# выберем элементы с четвертого по шестой +print(multi_string[3:6]) + +# выведем все элементы вплоть до второго +print(multi_string[:2]) + +# а также все элементы, начиная с четвертого +print(multi_string[3:]) + +# + +# Циклы в строках + +# выведем буквы в слове Питон +for i in "Питон": + print(i) + +# + +# Методы .strip() и .split() + +text = " Пример строки для методов strip и split. " +print(text.strip()) # убирает пробелы в начале и конце строки +print(text.split()) # разбивает строку на слова по пробелам +# - + +# посчитаем количество слов в тексте (длину списка) +len(text.split()) + +# + +# Замена символа в строке + +# предположим данные содержатся вот в таком формате +data = "20,25" + +# теперь заменим ',' на '.' +data = data.replace(",", ".") + +# и преобразуем в число +data_float = float(data) +print(data_float) +print(type(data_float)) +# - + +# Логические значения +# создадим переменную и запишем в нее логическое значение True +# (обязательно с большой буквы) +bool_var = False +type(bool_var) + +# + +# напишем небольшую программу, которая будет показывать, +# какое значение содержится в переменной var + +if bool_var is True: + print("Значение переменной истинно") +else: + print("Значение переменной ложно") From 641d0981080973d2638c78af23421945601fe830 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sat, 20 Dec 2025 19:32:19 +0300 Subject: [PATCH 29/37] [TASK] Python #1 (https://github.com/SENATOROVAI/intro/issues/5) Closes https://github.com/SENATOROVAI/intro/issues/5 --- python/makarov/chapter13_pandas.ipynb | 5365 +++++++++++++++++ python/makarov/chapter13_pandas.py | 599 ++ python/makarov/chapter_10_numpy.ipynb | 3706 ++++++++++++ python/makarov/chapter_10_numpy.py | 768 +++ python/makarov/chapter_11_iterator.ipynb | 1175 ++++ python/makarov/chapter_11_iterator.py | 423 ++ python/makarov/chapter_12_decorators.ipynb | 1625 +++++ python/makarov/chapter_12_decorators.py | 791 +++ python/makarov/chapter_3_if_and_loops.ipynb | 424 ++ python/makarov/chapter_3_if_and_loops.py | 224 + python/makarov/chapter_4_files.ipynb | 3 +- python/makarov/chapter_4_files.py | 166 + python/makarov/chapter_5_datetime.ipynb | 889 +++ python/makarov/chapter_5_datetime.py | 312 + python/makarov/chapter_6_functions.ipynb | 958 +++ python/makarov/chapter_6_functions.py | 406 ++ python/makarov/chapter_7_list_tuple_set.ipynb | 1867 ++++++ python/makarov/chapter_7_list_tuple_set.py | 474 ++ python/makarov/chapter_8_dict.ipynb | 1220 ++++ python/makarov/chapter_8_dict.py | 529 ++ python/makarov/chapter_9_oop.ipynb | 1195 ++++ python/makarov/chapter_9_oop.py | 531 ++ python/oop_molchanov.ipynb | 1431 +++++ python/oop_molchanov.py | 411 ++ 24 files changed, 25491 insertions(+), 1 deletion(-) create mode 100644 python/makarov/chapter13_pandas.ipynb create mode 100644 python/makarov/chapter13_pandas.py create mode 100644 python/makarov/chapter_10_numpy.ipynb create mode 100644 python/makarov/chapter_10_numpy.py create mode 100644 python/makarov/chapter_11_iterator.ipynb create mode 100644 python/makarov/chapter_11_iterator.py create mode 100644 python/makarov/chapter_12_decorators.ipynb create mode 100644 python/makarov/chapter_12_decorators.py create mode 100644 python/makarov/chapter_3_if_and_loops.ipynb create mode 100644 python/makarov/chapter_3_if_and_loops.py create mode 100644 python/makarov/chapter_4_files.py create mode 100644 python/makarov/chapter_5_datetime.ipynb create mode 100644 python/makarov/chapter_5_datetime.py create mode 100644 python/makarov/chapter_6_functions.ipynb create mode 100644 python/makarov/chapter_6_functions.py create mode 100644 python/makarov/chapter_7_list_tuple_set.ipynb create mode 100644 python/makarov/chapter_7_list_tuple_set.py create mode 100644 python/makarov/chapter_8_dict.ipynb create mode 100644 python/makarov/chapter_8_dict.py create mode 100644 python/makarov/chapter_9_oop.ipynb create mode 100644 python/makarov/chapter_9_oop.py create mode 100644 python/oop_molchanov.ipynb create mode 100644 python/oop_molchanov.py diff --git a/python/makarov/chapter13_pandas.ipynb b/python/makarov/chapter13_pandas.ipynb new file mode 100644 index 00000000..321998e0 --- /dev/null +++ b/python/makarov/chapter13_pandas.ipynb @@ -0,0 +1,5365 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "a633ecc2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Библиотека Pandas.'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"\"\"Библиотека Pandas.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e446a3d", + "metadata": {}, + "outputs": [], + "source": [ + "# импортируем модуль sqlite3 для работы с базой данных SQL\n", + "import sqlite3 as sql\n", + "\n", + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3c1b819", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
\n", + "
" + ], + "text/plain": [ + " PassengerId Survived Pclass \\\n", + "0 1 0 3 \n", + "1 2 1 1 \n", + "2 3 1 3 \n", + "\n", + " Name Sex Age SibSp \\\n", + "0 Braund, Mr. Owen Harris male 22.0 1 \n", + "1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 \n", + "2 Heikkinen, Miss. Laina female 26.0 0 \n", + "\n", + " Parch Ticket Fare Cabin Embarked \n", + "0 0 A/5 21171 7.2500 NaN S \n", + "1 0 PC 17599 71.2833 C85 C \n", + "2 0 STON/O2. 3101282 7.9250 NaN S " + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Создание датафрейма\n", + "# Способ 1. Создание датафрейма из файла\n", + "\n", + "# функция read_csv() распознает zip-архивы,\n", + "# в архиве может содержаться только один файл\n", + "csv_zip = pd.read_csv(r\"K:\\Storage\\makarov\\train.zip\")\n", + "csv_zip.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "20c65af1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
Petal_widthPetal_lengthSepal_widthSepal_lengthSpecies_name
00.21.43.55.1Setosa
10.21.43.04.9Setosa
20.21.33.24.7Setosa
\n", + "
" + ], + "text/plain": [ + " Petal_width Petal_length Sepal_width Sepal_length Species_name\n", + "0 0.2 1.4 3.5 5.1 Setosa\n", + "1 0.2 1.4 3.0 4.9 Setosa\n", + "2 0.2 1.3 3.2 4.7 Setosa" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# импортируем данные в формате Excel, указав номер листа,\n", + "# который хотим использовать\n", + "excel_data = pd.read_excel(r\"K:\\Storage\\makarov\\iris.xlsx\", sheet_name=0)\n", + "excel_data.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "826dda70", + "metadata": {}, + "outputs": [], + "source": [ + "# Файл в формате html.\n", + "# передадим соответствующую ссылку в функцию pd.read_html()\n", + "# в параметре match укажем ключевые слова, которые\n", + "# помогут найти нужную таблицу\n", + "html_data = pd.read_html(\n", + " \"https://en.wikipedia.org/wiki/World_population\", match=\"World population\"\n", + ")\n", + "\n", + "# # мы получили пять результатов\n", + "len(html_data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a391b59f", + "metadata": {}, + "outputs": [], + "source": [ + "html_data[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b71996fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
TrackIdNameAlbumIdMediaTypeIdGenreIdComposerMillisecondsBytesUnitPrice
01For Those About To Rock (We Salute You)111Angus Young, Malcolm Young, Brian Johnson343719111703340.99
12Balls to the Wall221None34256255104240.99
23Fast As a Shark321F. Baltes, S. Kaufman, U. Dirkscneider & W. Ho...23061939909940.99
\n", + "
" + ], + "text/plain": [ + " TrackId Name AlbumId MediaTypeId \\\n", + "0 1 For Those About To Rock (We Salute You) 1 1 \n", + "1 2 Balls to the Wall 2 2 \n", + "2 3 Fast As a Shark 3 2 \n", + "\n", + " GenreId Composer Milliseconds \\\n", + "0 1 Angus Young, Malcolm Young, Brian Johnson 343719 \n", + "1 1 None 342562 \n", + "2 1 F. Baltes, S. Kaufman, U. Dirkscneider & W. Ho... 230619 \n", + "\n", + " Bytes UnitPrice \n", + "0 11170334 0.99 \n", + "1 5510424 0.99 \n", + "2 3990994 0.99 " + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Способ 2. Подключение к базе данных SQL\n", + "\n", + "\n", + "# создадим соединение с базой данных chinook\n", + "conn = sql.connect(r\"K:\\Storage\\makarov\\chinook.db\")\n", + "\n", + "# выберем все строки из таблицы tracks\n", + "sql_data = pd.read_sql(\"SELECT * FROM tracks;\", conn) # vs. read_sql_query\n", + "\n", + "# посмотрим на результат\n", + "sql_data.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61fbd37b", + "metadata": {}, + "outputs": [], + "source": [ + "# Способ 3. Создание датафрейма из словаря\n", + "\n", + "# создадим несколько списков и массивов Numpy\n", + "# с информацией о семи странах мира\n", + "country = np.array(\n", + " [\n", + " \"China\",\n", + " \"Vietnam\",\n", + " \"United Kingdom\",\n", + " \"Russia\",\n", + " \"Argentina\",\n", + " \"Bolivia\",\n", + " \"South Africa\",\n", + " ]\n", + ")\n", + "capital = [\"Beijing\", \"Hanoi\", \"London\", \"Moscow\", \"Buenos Aires\", \"Sucre\", \"Pretoria\"]\n", + "population = [1400, 97, 67, 144, 45, 12, 59] # млн. человек\n", + "area = [9.6, 0.3, 0.2, 17.1, 2.8, 1.1, 1.2] # млн. кв. км.\n", + "sea = [1] * 5 + [0, 1] # выход к морю (в этом списке его нет только у Боливии)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f7eab71", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим пустой словарь\n", + "countries_dict = {}\n", + "\n", + "# превратим эти списки в значения словаря,\n", + "# одновременно снабдив необходимыми ключами\n", + "countries_dict[\"country\"] = country\n", + "countries_dict[\"capital\"] = capital # type: ignore[assignment]\n", + "countries_dict[\"population\"] = population # type: ignore[assignment]\n", + "countries_dict[\"area\"] = area # type: ignore[assignment]\n", + "countries_dict[\"sea\"] = sea # type: ignore[assignment]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c156a44b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'country': array(['China', 'Vietnam', 'United Kingdom', 'Russia', 'Argentina',\n", + " 'Bolivia', 'South Africa'], dtype='\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", + "
countrycapitalpopulationareasea
0ChinaBeijing14009.61
1VietnamHanoi970.31
2United KingdomLondon670.21
3RussiaMoscow14417.11
4ArgentinaBuenos Aires452.81
5BoliviaSucre121.10
6South AfricaPretoria591.21
\n", + "" + ], + "text/plain": [ + " country capital population area sea\n", + "0 China Beijing 1400 9.6 1\n", + "1 Vietnam Hanoi 97 0.3 1\n", + "2 United Kingdom London 67 0.2 1\n", + "3 Russia Moscow 144 17.1 1\n", + "4 Argentina Buenos Aires 45 2.8 1\n", + "5 Bolivia Sucre 12 1.1 0\n", + "6 South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# создадим датафрейм\n", + "countries = pd.DataFrame(countries_dict)\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "caec965b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
012
0111
1222
2333
\n", + "
" + ], + "text/plain": [ + " 0 1 2\n", + "0 1 1 1\n", + "1 2 2 2\n", + "2 3 3 3" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Способ 4. Создание датафрейма из 2D массива Numpy\n", + "\n", + "# внешнее измерение будет столбцами, внутренее - строками\n", + "arr = np.array([[1, 1, 1], [2, 2, 2], [3, 3, 3]])\n", + "\n", + "pd.DataFrame(arr)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "173a12e9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Index(['country', 'capital', 'population', 'area', 'sea'], dtype='object')" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Структура и свойства датафрейма\n", + "# Датафрейм библиотеки Pandas состоит из трех основных компонентов:\n", + "# строк (index), столбцов (columns) и значений (values).\n", + "\n", + "# выведем столбцы датафрейма countries\n", + "countries.columns" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "0062ad5e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "RangeIndex(start=0, stop=7, step=1)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# в нашем случае индекс это числовая последовательность\n", + "countries.index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30f3b44a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([['China', 'Beijing', 1400, 9.6, 1],\n", + " ['Vietnam', 'Hanoi', 97, 0.3, 1],\n", + " ['United Kingdom', 'London', 67, 0.2, 1],\n", + " ['Russia', 'Moscow', 144, 17.1, 1],\n", + " ['Argentina', 'Buenos Aires', 45, 2.8, 1],\n", + " ['Bolivia', 'Sucre', 12, 1.1, 0],\n", + " ['South Africa', 'Pretoria', 59, 1.2, 1]], dtype=object)" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# значения выводятся в формате массива Numpy\n", + "countries.values" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e95988d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "RangeIndex(start=0, stop=7, step=1)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем описание индекса датафрейма через атрибут axes[0]\n", + "countries.axes[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "e5691761", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Index(['country', 'capital', 'population', 'area', 'sea'], dtype='object')" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# axes[1] выводит названия столбцов\n", + "countries.axes[1]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "58cac5ec", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(2, (7, 5), 35)" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "countries.ndim, countries.shape, countries.size" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "00ffdf47", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "country object\n", + "capital object\n", + "population int64\n", + "area float64\n", + "sea int64\n", + "dtype: object" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Атрибут dtypes выдает тип данных каждого столбца.\n", + "\n", + "countries.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "e82ef9fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Index 132\n", + "country 56\n", + "capital 56\n", + "population 56\n", + "area 56\n", + "sea 56\n", + "dtype: int64" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Кроме того, с помощью метода .memory_usage() мы можем посмотреть\n", + "# объем занимаемой памяти по столбцам в байтах.\n", + "\n", + "countries.memory_usage()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34ffdf58", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
RURussiaMoscow14417.11
ARArgentinaBuenos Aires452.81
BOBoliviaSucre121.10
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1\n", + "RU Russia Moscow 144 17.1 1\n", + "AR Argentina Buenos Aires 45 2.8 1\n", + "BO Bolivia Sucre 12 1.1 0\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Индекс датафрейма\n", + "# Присвоение индекса\n", + "\n", + "# создадим список с кодами стран\n", + "custom_index = [\"CN\", \"VN\", \"GB\", \"RU\", \"AR\", \"BO\", \"ZA\"]\n", + "\n", + "# создадим датафрейм из словаря и передадим список через параметр index\n", + "countries = pd.DataFrame(countries_dict, index=custom_index)\n", + "\n", + "# посмотрим на результат\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "1726b6d3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
indexcountrycapitalpopulationareasea
0CNChinaBeijing14009.61
1VNVietnamHanoi970.31
2GBUnited KingdomLondon670.21
3RURussiaMoscow14417.11
4ARArgentinaBuenos Aires452.81
5BOBoliviaSucre121.10
6ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " index country capital population area sea\n", + "0 CN China Beijing 1400 9.6 1\n", + "1 VN Vietnam Hanoi 97 0.3 1\n", + "2 GB United Kingdom London 67 0.2 1\n", + "3 RU Russia Moscow 144 17.1 1\n", + "4 AR Argentina Buenos Aires 45 2.8 1\n", + "5 BO Bolivia Sucre 12 1.1 0\n", + "6 ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Этот индекс можно сбросить с помощью метода .reset_index().\n", + "\n", + "# при этом параметр inplace = True сделает изменения постоянными\n", + "countries.reset_index(inplace=True)\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "52b9d667", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
index
CNChinaBeijing14009.61
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
RURussiaMoscow14417.11
ARArgentinaBuenos Aires452.81
BOBoliviaSucre121.10
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "index \n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1\n", + "RU Russia Moscow 144 17.1 1\n", + "AR Argentina Buenos Aires 45 2.8 1\n", + "BO Bolivia Sucre 12 1.1 0\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Как мы видим, прошлый индекс стал отдельным столбцом.\n", + "# Снова сделаем этот столбец индексом\n", + "# с помощью метода .set_index().\n", + "\n", + "# передадим методу название столбца, который хотим сделать индексом\n", + "countries.set_index(\"index\", inplace=True)\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "566b89fe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
0ChinaBeijing14009.61
1VietnamHanoi970.31
2United KingdomLondon670.21
3RussiaMoscow14417.11
4ArgentinaBuenos Aires452.81
5BoliviaSucre121.10
6South AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "0 China Beijing 1400 9.6 1\n", + "1 Vietnam Hanoi 97 0.3 1\n", + "2 United Kingdom London 67 0.2 1\n", + "3 Russia Moscow 144 17.1 1\n", + "4 Argentina Buenos Aires 45 2.8 1\n", + "5 Bolivia Sucre 12 1.1 0\n", + "6 South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Еще раз сбросим индекс, но на этот раз не будем делать его отдельным\n", + "# столбцом. Для этого передадим методу .reset_index() параметр drop = True.\n", + "\n", + "countries.reset_index(drop=True, inplace=True)\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "da9b5ef7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
RURussiaMoscow14417.11
ARArgentinaBuenos Aires452.81
BOBoliviaSucre121.10
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1\n", + "RU Russia Moscow 144 17.1 1\n", + "AR Argentina Buenos Aires 45 2.8 1\n", + "BO Bolivia Sucre 12 1.1 0\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Собственный индекс можно создать, просто поместив\n", + "# новые значения в атрибут index.\n", + "\n", + "\n", + "countries.index = custom_index\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "2d9653b7", + "metadata": {}, + "outputs": [], + "source": [ + "# Многоуровневый индекс\n", + "\n", + "# Многоуровневый (MultiIndex) или иерархический (hierarchical) индекс\n", + "# позволяет задать несколько уровней (levels) для индексации строк или столбцов\n", + "\n", + "# создадим список из кортежей с названием континента и кодом страны\n", + "rows = [\n", + " (\"Asia\", \"CN\"),\n", + " (\"Asia\", \"VN\"),\n", + " (\"Europe\", \"GB\"),\n", + " (\"Europe\", \"RU\"),\n", + " (\"S. America\", \"AR\"),\n", + " (\"S. America\", \"BO\"),\n", + " (\"Africa\", \"ZA\"),\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "63648d93", + "metadata": {}, + "outputs": [], + "source": [ + "# в столбцах название страны и столицы мы объединим в категорию names\n", + "# а размер населения, площадь и выход к морю в data\n", + "\n", + "cols = [\n", + " (\"names\", \"country\"),\n", + " (\"names\", \"capital\"),\n", + " (\"data\", \"population\"),\n", + " (\"data\", \"area\"),\n", + " (\"data\", \"sea\"),\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "ae279ff4", + "metadata": {}, + "outputs": [], + "source": [ + "# Теперь создадим иерархический индекс для строк и столбцов\n", + "# с помощью функции pd.MultiIndex.from_tuples().\n", + "\n", + "# создадим многоуровневый индекс для строк\n", + "# индексам присвоим названия через names = ['region', 'code']\n", + "custom_multindex = pd.MultiIndex.from_tuples(rows, names=[\"region\", \"code\"])\n", + "\n", + "# сделаем то же самое для столбцов\n", + "custom_multicols = pd.MultiIndex.from_tuples(cols)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "31974fd8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
namesdata
countrycapitalpopulationareasea
regioncode
AsiaCNChinaBeijing14009.61
VNVietnamHanoi970.31
EuropeGBUnited KingdomLondon670.21
RURussiaMoscow14417.11
S. AmericaARArgentinaBuenos Aires452.81
BOBoliviaSucre121.10
AfricaZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " names data \n", + " country capital population area sea\n", + "region code \n", + "Asia CN China Beijing 1400 9.6 1\n", + " VN Vietnam Hanoi 97 0.3 1\n", + "Europe GB United Kingdom London 67 0.2 1\n", + " RU Russia Moscow 144 17.1 1\n", + "S. America AR Argentina Buenos Aires 45 2.8 1\n", + " BO Bolivia Sucre 12 1.1 0\n", + "Africa ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "countries.index = custom_multindex\n", + "countries.columns = custom_multicols\n", + "\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "790c2948", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
RURussiaMoscow14417.11
ARArgentinaBuenos Aires452.81
BOBoliviaSucre121.10
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1\n", + "RU Russia Moscow 144 17.1 1\n", + "AR Argentina Buenos Aires 45 2.8 1\n", + "BO Bolivia Sucre 12 1.1 0\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# вернемся к обычному индексу и названиям столбцов\n", + "custom_cols = [\"country\", \"capital\", \"population\", \"area\", \"sea\"]\n", + "\n", + "countries.index = custom_index\n", + "countries.columns = custom_cols\n", + "\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4237cf92", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'country': {'CN': 'China', 'VN': 'Vietnam', 'GB': 'United Kingdom', 'RU': 'Russia', 'AR': 'Argentina', 'BO': 'Bolivia', 'ZA': 'South Africa'}, 'capital': {'CN': 'Beijing', 'VN': 'Hanoi', 'GB': 'London', 'RU': 'Moscow', 'AR': 'Buenos Aires', 'BO': 'Sucre', 'ZA': 'Pretoria'}, 'population': {'CN': 1400, 'VN': 97, 'GB': 67, 'RU': 144, 'AR': 45, 'BO': 12, 'ZA': 59}, 'area': {'CN': 9.6, 'VN': 0.3, 'GB': 0.2, 'RU': 17.1, 'AR': 2.8, 'BO': 1.1, 'ZA': 1.2}, 'sea': {'CN': 1, 'VN': 1, 'GB': 1, 'RU': 1, 'AR': 1, 'BO': 0, 'ZA': 1}}\n" + ] + } + ], + "source": [ + "# Преобразование в другие форматы\n", + "# Получившийся датафрейм можно преобразовать в словарь с помощью метода .to_dict().\n", + "\n", + "print(countries.to_dict())" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "a6aab3f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([['China', 'Beijing', 1400, 9.6, 1],\n", + " ['Vietnam', 'Hanoi', 97, 0.3, 1],\n", + " ['United Kingdom', 'London', 67, 0.2, 1],\n", + " ['Russia', 'Moscow', 144, 17.1, 1],\n", + " ['Argentina', 'Buenos Aires', 45, 2.8, 1],\n", + " ['Bolivia', 'Sucre', 12, 1.1, 0],\n", + " ['South Africa', 'Pretoria', 59, 1.2, 1]], dtype=object)" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Аналогично, метод .to_numpy() преобразовывает данные в массив Numpy.\n", + "\n", + "countries.to_numpy()" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "1fb1191c", + "metadata": {}, + "outputs": [], + "source": [ + "# по умолчанию, индекс также станет частью .csv файла\n", + "# параметр index = False позволит этого избежать\n", + "countries.to_csv(\"countries.csv\", index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "4756055f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['China', 'Vietnam', 'United Kingdom', 'Russia', 'Argentina', 'Bolivia', 'South Africa']\n" + ] + } + ], + "source": [ + "# Метод .to_list() позволяет преобразовать объект Series\n", + "# (столбец датафрейма) в список.\n", + "\n", + "print(countries.country.to_list())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a11cc342", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание Series\n", + "country_list = [\n", + " \"China\",\n", + " \"South Africa\",\n", + " \"United Kingdom\",\n", + " \"Russia\",\n", + " \"Argentina\",\n", + " \"Vietnam\",\n", + " \"Australia\",\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2ec82ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0 China\n", + "1 South Africa\n", + "2 United Kingdom\n", + "3 Russia\n", + "4 Argentina\n", + "5 Vietnam\n", + "6 Australia\n", + "dtype: object" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "country_series = pd.Series(country_list)\n", + "country_series" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf7b4031", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'China'" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# например, выведем первый элемент\n", + "country_series[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "b57ca386", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание Series из словаря\n", + "\n", + "country_dict = {\n", + " \"CN\": \"China\",\n", + " \"ZA\": \"South Africa\",\n", + " \"GB\": \"United Kingdom\",\n", + " \"RU\": \"Russia\",\n", + " \"AR\": \"Argentina\",\n", + " \"VN\": \"Vietnam\",\n", + " \"AU\": \"Australia\",\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a076933", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CN China\n", + "ZA South Africa\n", + "GB United Kingdom\n", + "RU Russia\n", + "AR Argentina\n", + "VN Vietnam\n", + "AU Australia\n", + "dtype: object" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "country_series = pd.Series(country_dict)\n", + "country_series" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c499de9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Australia'" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "country_series[\"AU\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d9e8023", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "country\n", + "capital\n", + "population\n", + "area\n", + "sea\n" + ] + } + ], + "source": [ + "# Доступ к строкам, столбцам и элементам\n", + "# Циклы в датафрейме\n", + "# Цикл for позволяет получить доступ к названиям столбцов датафрейма.\n", + "\n", + "for column in countries:\n", + " print(column)" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "b6d6fd8f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CN\n", + "country China\n", + "capital Beijing\n", + "population 1400\n", + "area 9.6\n", + "sea 1\n", + "Name: CN, dtype: object\n", + "...\n", + "\n" + ] + } + ], + "source": [ + "# Метод .iterrows() возвращает индекс строки и ее содержимое в формате Series.\n", + "\n", + "# прервем цикл после первой итерации с помощью break\n", + "for index, row in countries.iterrows():\n", + " print(index)\n", + " print(row)\n", + " print(\"...\")\n", + " print(type(row))\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "caf35efc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Beijing is the capital of China\n" + ] + } + ], + "source": [ + "# Получить доступ к элементам одной строки можно\n", + "# по индексу объекта Series\n", + "# (то есть по названиям столбцов исходного датафрейма).\n", + "\n", + "for _, row in countries.iterrows():\n", + " # например, сформируем вот такое предложение\n", + " print(row[\"capital\"] + \" is the capital of \" + row[\"country\"])\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "47478a2b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CN Beijing\n", + "VN Hanoi\n", + "GB London\n", + "RU Moscow\n", + "AR Buenos Aires\n", + "BO Sucre\n", + "ZA Pretoria\n", + "Name: capital, dtype: object" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Доступ к столбцам\n", + "\n", + "# выведем столбец capital датафрейма countries\n", + "countries[\"capital\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "0627f167", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CN Beijing\n", + "VN Hanoi\n", + "GB London\n", + "RU Moscow\n", + "AR Buenos Aires\n", + "BO Sucre\n", + "ZA Pretoria\n", + "Name: capital, dtype: object" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# однако в этом случае название не должно содержать пробелов\n", + "countries.capital" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "2323aef9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pandas.core.series.Series" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Как уже было сказано, отдельные столбцы в датафрейме имеют тип данных Series.\n", + "type(countries.capital)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "87e2aece", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
capital
CNBeijing
VNHanoi
GBLondon
RUMoscow
ARBuenos Aires
BOSucre
ZAPretoria
\n", + "
" + ], + "text/plain": [ + " capital\n", + "CN Beijing\n", + "VN Hanoi\n", + "GB London\n", + "RU Moscow\n", + "AR Buenos Aires\n", + "BO Sucre\n", + "ZA Pretoria" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Для того чтобы получить доступ к столбцам и\n", + "# при этом на выходе сформировать датафрейм,\n", + "# необходимо использовать двойные скобки.\n", + "\n", + "# логика здесь в том, что внутрениие скобки - это список,\n", + "# внешние - оператор индексации\n", + "countries[[\"capital\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "6d94d3a5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
capitalarea
CNBeijing9.6
VNHanoi0.3
GBLondon0.2
RUMoscow17.1
ARBuenos Aires2.8
BOSucre1.1
ZAPretoria1.2
\n", + "
" + ], + "text/plain": [ + " capital area\n", + "CN Beijing 9.6\n", + "VN Hanoi 0.3\n", + "GB London 0.2\n", + "RU Moscow 17.1\n", + "AR Buenos Aires 2.8\n", + "BO Sucre 1.1\n", + "ZA Pretoria 1.2" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "countries[[\"capital\", \"area\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "f6b3705e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
capitalpopulation
CNBeijing1400
VNHanoi97
GBLondon67
RUMoscow144
ARBuenos Aires45
BOSucre12
ZAPretoria59
\n", + "
" + ], + "text/plain": [ + " capital population\n", + "CN Beijing 1400\n", + "VN Hanoi 97\n", + "GB London 67\n", + "RU Moscow 144\n", + "AR Buenos Aires 45\n", + "BO Sucre 12\n", + "ZA Pretoria 59" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Доступ к столбцам можно также получить с помощью метода\n", + "# .filter(), передав параметру items список с\n", + "# необходимыми нам столбцами.\n", + "\n", + "countries.filter(items=[\"capital\", \"population\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "edcade7f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
RURussiaMoscow14417.11
ARArgentinaBuenos Aires452.81
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1\n", + "RU Russia Moscow 144 17.1 1\n", + "AR Argentina Buenos Aires 45 2.8 1" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Доступ к строкам\n", + "\n", + "# выведем строки со второй по пятую (не включительно)\n", + "countries[1:5]" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "87a2ed1e", + "metadata": {}, + "outputs": [], + "source": [ + "# Методы .loc[] и .iloc[]\n", + "# Метод .loc[] позволяет получить доступ к строкам и столбцам через\n", + "# их названия (label-based location)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "47e9df46", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
capitalpopulationarea
CNBeijing14009.6
RUMoscow14417.1
VNHanoi970.3
\n", + "
" + ], + "text/plain": [ + " capital population area\n", + "CN Beijing 1400 9.6\n", + "RU Moscow 144 17.1\n", + "VN Hanoi 97 0.3" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# для этого передадим методу .loc[] два списка:\n", + "# с индексами строк и названиями столбцов\n", + "countries.loc[[\"CN\", \"RU\", \"VN\"], [\"capital\", \"population\", \"area\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "cc7c5c40", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
capitalpopulationarea
CNBeijing14009.6
VNHanoi970.3
GBLondon670.2
RUMoscow14417.1
ARBuenos Aires452.8
BOSucre121.1
ZAPretoria591.2
\n", + "
" + ], + "text/plain": [ + " capital population area\n", + "CN Beijing 1400 9.6\n", + "VN Hanoi 97 0.3\n", + "GB London 67 0.2\n", + "RU Moscow 144 17.1\n", + "AR Buenos Aires 45 2.8\n", + "BO Sucre 12 1.1\n", + "ZA Pretoria 59 1.2" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Через двоеточие, как и в Numpy, мы можем вывести\n", + "# все строки или все столбцы датафрейма\n", + "\n", + "\n", + "# например, выведем все строки датафрейма\n", + "countries.loc[:, [\"capital\", \"population\", \"area\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "6bf16382", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
sea
CN1
VN1
GB1
RU1
AR1
BO0
ZA1
\n", + "
" + ], + "text/plain": [ + " sea\n", + "CN 1\n", + "VN 1\n", + "GB 1\n", + "RU 1\n", + "AR 1\n", + "BO 0\n", + "ZA 1" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .loc[] также поддерживает значения Boolean\n", + "\n", + "\n", + "# например, выведем все строки и только последний столбец,\n", + "# передав список соответствующих логических значений\n", + "countries.loc[:, [False, False, False, False, True]]" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "52bd052e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .get_loc() позволяет узнать порядковый номер\n", + "# (начиная с нуля) строки или столбца по их индексу\n", + "# и названию соответственно.\n", + "\n", + "# выведем номер строки с индексом RU\n", + "countries.index.get_loc(\"RU\")" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "ed674046", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "countries.columns.get_loc(\"country\")" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "ec683b8c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulation
CNChinaBeijing1400
RURussiaMoscow144
BOBoliviaSucre12
\n", + "
" + ], + "text/plain": [ + " country capital population\n", + "CN China Beijing 1400\n", + "RU Russia Moscow 144\n", + "BO Bolivia Sucre 12" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .iloc[]\n", + "# Метод .iloc[] действует примерно также, как и .loc[],\n", + "# с тем отличием, что он основывается на числовом индексе\n", + "# (integer-based location).\n", + "\n", + "# теперь в списки мы передаем номера строк и столбцов,\n", + "# нумерация начинается с нуля\n", + "countries.iloc[[0, 3, 5], [0, 1, 2]]" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "2251352b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
areasea
CN9.61
VN0.31
GB0.21
\n", + "
" + ], + "text/plain": [ + " area sea\n", + "CN 9.6 1\n", + "VN 0.3 1\n", + "GB 0.2 1" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем первые три строки и последние два столбца\n", + "countries.iloc[:3, -2:]" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "cf3f6b59", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
populationarea
CN14009.6
RU14417.1
\n", + "
" + ], + "text/plain": [ + " population area\n", + "CN 1400 9.6\n", + "RU 144 17.1" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# К столбцам удобно обращаться по их названиям, к строкам —\n", + "# по порядковому номеру (числовому индексу).\n", + "\n", + "# вначале передадим названия столбцов в двойных скобках,\n", + "# затем номера строк через метод .iloc[]\n", + "countries[[\"population\", \"area\"]].iloc[[0, 3]]" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "24b3d8f8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
namesdata
countrycapitalpopulationareasea
regioncode
AsiaCNChinaBeijing14009.61
VNVietnamHanoi970.31
EuropeGBUnited KingdomLondon670.21
RURussiaMoscow14417.11
S. AmericaARArgentinaBuenos Aires452.81
BOBoliviaSucre121.10
AfricaZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " names data \n", + " country capital population area sea\n", + "region code \n", + "Asia CN China Beijing 1400 9.6 1\n", + " VN Vietnam Hanoi 97 0.3 1\n", + "Europe GB United Kingdom London 67 0.2 1\n", + " RU Russia Moscow 144 17.1 1\n", + "S. America AR Argentina Buenos Aires 45 2.8 1\n", + " BO Bolivia Sucre 12 1.1 0\n", + "Africa ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Многоуровневый индекс и методы .loc[] и .iloc[]\n", + "# вновь создадим датафрейм с многоуровневым индексом по строкам и столбцам\n", + "countries.index = custom_multindex\n", + "countries.columns = custom_multicols\n", + "\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41071c0e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "names country China\n", + " capital Beijing\n", + "data population 1400\n", + " area 9.6\n", + " sea 1\n", + "Name: (Asia, CN), dtype: object" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Для доступа к первой строке передадим методу\n", + "# .loc[] соответствующий двойной индекс.\n", + "\n", + "countries.loc[\"Asia\", \"CN\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "3b2f6a64", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "data population 1400.0\n", + " area 9.6\n", + " sea 1.0\n", + "Name: (Asia, CN), dtype: float64" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем первую строку и столбцы с числовыми данными\n", + "countries.loc[\n", + " (\"Asia\", \"CN\"), [(\"data\", \"population\"), (\"data\", \"area\"), (\"data\", \"sea\")]\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "e0767808", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
namesdata
countrycapitalpopulationareasea
regioncode
AsiaCNChinaBeijing14009.61
VNVietnamHanoi970.31
\n", + "
" + ], + "text/plain": [ + " names data \n", + " country capital population area sea\n", + "region code \n", + "Asia CN China Beijing 1400 9.6 1\n", + " VN Vietnam Hanoi 97 0.3 1" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Доступ к строкам можно получить, указав внутри кортежа\n", + "# название региона и список с кодами стран.\n", + "\n", + "countries.loc[(\"Asia\", [\"CN\", \"VN\"]), :]" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "6eedddf0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
namesdata
countrycapitalpopulationareasea
code
CNChinaBeijing14009.61
VNVietnamHanoi970.31
\n", + "
" + ], + "text/plain": [ + " names data \n", + " country capital population area sea\n", + "code \n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Внутри кортежа можно указать только регион. В этом случае\n", + "# мы получим все находящиеся в нем страны.\n", + "\n", + "countries.loc[(\"Asia\"), :]" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "7f4e585a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
namesdata
countrypopulation
regioncode
AsiaCNChina1400
VNVietnam97
EuropeGBUnited Kingdom67
RURussia144
S. AmericaARArgentina45
BOBolivia12
AfricaZASouth Africa59
\n", + "
" + ], + "text/plain": [ + " names data\n", + " country population\n", + "region code \n", + "Asia CN China 1400\n", + " VN Vietnam 97\n", + "Europe GB United Kingdom 67\n", + " RU Russia 144\n", + "S. America AR Argentina 45\n", + " BO Bolivia 12\n", + "Africa ZA South Africa 59" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Аналогичным образом мы можем получить доступ к столбцам.\n", + "\n", + "countries.loc[:, [(\"names\", \"country\"), (\"data\", \"population\")]]" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "e8ca3b1e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "data population 144.0\n", + " area 17.1\n", + " sea 1.0\n", + "Name: (Europe, RU), dtype: float64" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .iloc[], при этом, игнорирует структуру иерархического индекса\n", + "# и использует простой числовой индекс.\n", + "\n", + "# получим доступ к четвертой строке и третьему, четвертому и пятому столбцам\n", + "countries.iloc[3, [2, 3, 4]]" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "57339c7a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
namesdata
countrycapitalpopulationareasea
code
GBUnited KingdomLondon670.21
RURussiaMoscow14417.11
\n", + "
" + ], + "text/plain": [ + " names data \n", + " country capital population area sea\n", + "code \n", + "GB United Kingdom London 67 0.2 1\n", + "RU Russia Moscow 144 17.1 1" + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .xs()\n", + "# Метод .xs() (от англ. cross-section, срез) позволяет получить доступ\n", + "# к определенному уровню иерархического индекса. Начнем со строк.\n", + "\n", + "# выберем Европу из уровня region\n", + "# axis = 0 указывает, что мы берем строки\n", + "countries.xs(\"Europe\", level=\"region\", axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03192b46", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
names
country
regioncode
AsiaCNChina
VNVietnam
EuropeGBUnited Kingdom
RURussia
S. AmericaARArgentina
BOBolivia
AfricaZASouth Africa
\n", + "
" + ], + "text/plain": [ + " names\n", + " country\n", + "region code \n", + "Asia CN China\n", + " VN Vietnam\n", + "Europe GB United Kingdom\n", + " RU Russia\n", + "S. America AR Argentina\n", + " BO Bolivia\n", + "Africa ZA South Africa" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# levels указывает, на каких уровнях искать названия столбцов\n", + "# параметр axis = 1 говорит о том, что мы имеем дело со столбцами\n", + "countries.xs((\"names\", \"country\"), axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f771d41", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapital
code
GBUnited KingdomLondon
RURussiaMoscow
\n", + "
" + ], + "text/plain": [ + " country capital\n", + "code \n", + "GB United Kingdom London\n", + "RU Russia Moscow" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Кроме того, мы можем соединить два метода .xs() и одновременно\n", + "# получить доступ и к строкам, и к столбцам.\n", + "\n", + "# в данном случае мы можем не указывать level, потому что\n", + "# Europe и names находятся во внешних индексах,\n", + "# которые в level указаны по умолчанию\n", + "countries_xs = countries.xs(\"Europe\", axis=0).loc[:, \"names\"]\n", + "countries_xs" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "b2f33e3d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
RURussiaMoscow14417.11
ARArgentinaBuenos Aires452.81
BOBoliviaSucre121.10
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1\n", + "RU Russia Moscow 144 17.1 1\n", + "AR Argentina Buenos Aires 45 2.8 1\n", + "BO Bolivia Sucre 12 1.1 0\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Вернем датафрейму одноуровневый индекс.\n", + "\n", + "# обновим атрибуты index и columns\n", + "countries.index = custom_index\n", + "countries.columns = custom_cols\n", + "\n", + "# посмотрим на исходный датафрейм\n", + "countries" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "6a246031", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Beijing'" + ] + }, + "execution_count": 77, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .at[]\n", + "# Метод .at[] подходит для извлечения или записи одного значения датафрейма.\n", + "\n", + "countries.at[\"CN\", \"capital\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dcada3bd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CN True\n", + "VN False\n", + "GB False\n", + "RU False\n", + "AR False\n", + "BO False\n", + "ZA False\n", + "Name: population, dtype: bool" + ] + }, + "execution_count": 78, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Фильтры\n", + "# создадим логическую маску для стран с населением больше миллиарда человек\n", + "countries.population > 1000" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "f1e5bc60", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# применим логическую маску к исходному датафрейму\n", + "countries[countries.population > 1000]" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "e06768bd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 80, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# отфильтруем датафрейм по критериям численности населения и площади\n", + "countries[(countries.population > 50) & (countries.area < 2)]" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "cf81ebea", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
VNVietnamHanoi970.31
RURussiaMoscow14417.11
ARArgentinaBuenos Aires452.81
BOBoliviaSucre121.10
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "RU Russia Moscow 144 17.1 1\n", + "AR Argentina Buenos Aires 45 2.8 1\n", + "BO Bolivia Sucre 12 1.1 0" + ] + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# вначале создаем нужные нам маски\n", + "population_mask = countries.population > 70\n", + "area_mask = countries.population < 50\n", + "\n", + "# затем объединяем их по необходимым условиям (в данном случае ИЛИ)\n", + "mask = population_mask | area_mask\n", + "# и применяем маску к исходному датафрейму\n", + "countries[mask]" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "b2a624f4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .query()\n", + "# Метод .query() позволяет задавать условие фильтрации «своими словами».\n", + "\n", + "# например, выберем страны с населением более 50 млн. человек И\n", + "# площадью менее двух млн. кв. километров\n", + "countries.query(\"population > 50 and area < 2\")" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "915331b7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
countrycapitalpopulationareasea
GBUnited KingdomLondon670.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "GB United Kingdom London 67 0.2 1" + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем данные по Великобритании\n", + "countries.query(\"country == 'United Kingdom'\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c561437d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
VNVietnamHanoi970.31
RURussiaMoscow14417.11
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "RU Russia Moscow 144 17.1 1" + ] + }, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# С помощью метода .isin() мы можем проверить наличие нескольких значений\n", + "# в определенном столбце, а затем использовать результат\n", + "# в качестве логической маски.\n", + "\n", + "# найдем строки, в которых в столбце capital присутствуют следующие значения\n", + "keyword_list = [\"Beijing\", \"Moscow\", \"Hanoi\"]\n", + "\n", + "countries_print = countries[countries.capital.isin(keyword_list)]\n", + "print(countries_print)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3e8557f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
RURussiaMoscow14417.11
BOBoliviaSucre121.10
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1\n", + "RU Russia Moscow 144 17.1 1\n", + "BO Bolivia Sucre 12 1.1 0\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 85, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Похожим образом можно использовать метод .startswith().\n", + "# например, для нахождения стран, НЕ начинающихся с буквы \"A\"\n", + "countries_start = countries[~countries.country.str.startswith(\"A\")]\n", + "countries_start" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "af882838", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
RURussiaMoscow14417.11
VNVietnamHanoi970.31
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1\n", + "RU Russia Moscow 144 17.1 1\n", + "VN Vietnam Hanoi 97 0.3 1" + ] + }, + "execution_count": 86, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# например, возьмем три строки с наибольшими значением по столбцу population\n", + "countries.nlargest(3, \"population\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0aa75c40", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(3)" + ] + }, + "execution_count": 87, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .argmax() выводит индекс строки, в которой в\n", + "# определенном столбце содержится наибольшее значение.\n", + "\n", + "# например, предположим, что мы хотим найти индекс страны с наибольшей площадью\n", + "countries_max = countries.area.argmax()\n", + "countries_max" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "689c0e98", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
countrycapitalpopulationareasea
RURussiaMoscow14417.11
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "RU Russia Moscow 144 17.1 1" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Посмотрим, какой стране соответствует этот индекс.\n", + "\n", + "# напомню, что двойные скобки позволяют вывести DataFrame, а не Series\n", + "countries_area = countries.iloc[[countries.area.argmax()]]\n", + "countries_area" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "72d0f962", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
CNChinaBeijing14009.61
VNVietnamHanoi970.31
RURussiaMoscow14417.11
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "CN China Beijing 1400 9.6 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "RU Russia Moscow 144 17.1 1" + ] + }, + "execution_count": 89, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Вспомним, что в метод .loc[] можно передать тип данных Boolean.\n", + "# Используем это свойство для создания фильтра.\n", + "\n", + "# выведем страны с населением более 90 млн. человек\n", + "countries.loc[countries.population > 90, :]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8de844c4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
countrycapitalpopulationareasea
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 90, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .filter(), если использовать параметр like,\n", + "# ищет совпадения с искомой фразой в индексе\n", + "# (если axis = 0) или названии столбцов (если axis = 1).\n", + "\n", + "# найдем строки, в которых в индексе есть буквосочетание \"ZA\"\n", + "countries.filter(like=\"ZA\", axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5eee82c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
BOBoliviaSucre121.10
ARArgentinaBuenos Aires452.81
ZASouth AfricaPretoria591.21
GBUnited KingdomLondon670.21
VNVietnamHanoi970.31
RURussiaMoscow14417.11
CNChinaBeijing14009.61
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "BO Bolivia Sucre 12 1.1 0\n", + "AR Argentina Buenos Aires 45 2.8 1\n", + "ZA South Africa Pretoria 59 1.2 1\n", + "GB United Kingdom London 67 0.2 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "RU Russia Moscow 144 17.1 1\n", + "CN China Beijing 1400 9.6 1" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Сортировка\n", + "# выполним сортировку по столбцу population, не сохраняя изменений,\n", + "# в возрастающем порядке (значение по умолчанию)\n", + "countries.sort_values(by=\"population\", inplace=False, ascending=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "27f52f13", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
RURussiaMoscow14417.11
CNChinaBeijing14009.61
ARArgentinaBuenos Aires452.81
ZASouth AfricaPretoria591.21
BOBoliviaSucre121.10
VNVietnamHanoi970.31
GBUnited KingdomLondon670.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "RU Russia Moscow 144 17.1 1\n", + "CN China Beijing 1400 9.6 1\n", + "AR Argentina Buenos Aires 45 2.8 1\n", + "ZA South Africa Pretoria 59 1.2 1\n", + "BO Bolivia Sucre 12 1.1 0\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "GB United Kingdom London 67 0.2 1" + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# теперь отсортируем по двум столбцам в нисходящем порядке\n", + "countries.sort_values(by=[\"area\", \"population\"], inplace=False, ascending=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "9f6acb85", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
countrycapitalpopulationareasea
ARArgentinaBuenos Aires452.81
BOBoliviaSucre121.10
CNChinaBeijing14009.61
GBUnited KingdomLondon670.21
RURussiaMoscow14417.11
VNVietnamHanoi970.31
ZASouth AfricaPretoria591.21
\n", + "
" + ], + "text/plain": [ + " country capital population area sea\n", + "AR Argentina Buenos Aires 45 2.8 1\n", + "BO Bolivia Sucre 12 1.1 0\n", + "CN China Beijing 1400 9.6 1\n", + "GB United Kingdom London 67 0.2 1\n", + "RU Russia Moscow 144 17.1 1\n", + "VN Vietnam Hanoi 97 0.3 1\n", + "ZA South Africa Pretoria 59 1.2 1" + ] + }, + "execution_count": 93, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Кроме того, можно отсортировать строки по индексу с помощью метода .sort_index().\n", + "\n", + "countries.sort_index()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter13_pandas.py b/python/makarov/chapter13_pandas.py new file mode 100644 index 00000000..29aa7bad --- /dev/null +++ b/python/makarov/chapter13_pandas.py @@ -0,0 +1,599 @@ +"""Библиотека Pandas.""" + +# + +# импортируем модуль sqlite3 для работы с базой данных SQL +import sqlite3 as sql + +import numpy as np +import pandas as pd + +# + +# Создание датафрейма +# Способ 1. Создание датафрейма из файла + +# функция read_csv() распознает zip-архивы, +# в архиве может содержаться только один файл +csv_zip = pd.read_csv(r"K:\Storage\makarov\train.zip") +csv_zip.head(3) +# - + +# импортируем данные в формате Excel, указав номер листа, +# который хотим использовать +excel_data = pd.read_excel(r"K:\Storage\makarov\iris.xlsx", sheet_name=0) +excel_data.head(3) + +# + +# Файл в формате html. +# передадим соответствующую ссылку в функцию pd.read_html() +# в параметре match укажем ключевые слова, которые +# помогут найти нужную таблицу +html_data = pd.read_html( + "https://en.wikipedia.org/wiki/World_population", match="World population" +) + +# # мы получили пять результатов +len(html_data) +# - + +html_data[0] + +# + +# Способ 2. Подключение к базе данных SQL + + +# создадим соединение с базой данных chinook +conn = sql.connect(r"K:\Storage\makarov\chinook.db") + +# выберем все строки из таблицы tracks +sql_data = pd.read_sql("SELECT * FROM tracks;", conn) # vs. read_sql_query + +# посмотрим на результат +sql_data.head(3) + +# + +# Способ 3. Создание датафрейма из словаря + +# создадим несколько списков и массивов Numpy +# с информацией о семи странах мира +country = np.array( + [ + "China", + "Vietnam", + "United Kingdom", + "Russia", + "Argentina", + "Bolivia", + "South Africa", + ] +) +capital = ["Beijing", "Hanoi", "London", "Moscow", "Buenos Aires", "Sucre", "Pretoria"] +population = [1400, 97, 67, 144, 45, 12, 59] # млн. человек +area = [9.6, 0.3, 0.2, 17.1, 2.8, 1.1, 1.2] # млн. кв. км. +sea = [1] * 5 + [0, 1] # выход к морю (в этом списке его нет только у Боливии) + +# + +# создадим пустой словарь +countries_dict = {} + +# превратим эти списки в значения словаря, +# одновременно снабдив необходимыми ключами +countries_dict["country"] = country +countries_dict["capital"] = capital # type: ignore[assignment] +countries_dict["population"] = population # type: ignore[assignment] +countries_dict["area"] = area # type: ignore[assignment] +countries_dict["sea"] = sea # type: ignore[assignment] +# - + +# посмотрим на результат +countries_dict + +# создадим датафрейм +countries = pd.DataFrame(countries_dict) +countries + +# + +# Способ 4. Создание датафрейма из 2D массива Numpy + +# внешнее измерение будет столбцами, внутренее - строками +arr = np.array([[1, 1, 1], [2, 2, 2], [3, 3, 3]]) + +pd.DataFrame(arr) + +# + +# Структура и свойства датафрейма +# Датафрейм библиотеки Pandas состоит из трех основных компонентов: +# строк (index), столбцов (columns) и значений (values). + +# выведем столбцы датафрейма countries +countries.columns +# - + +# в нашем случае индекс это числовая последовательность +countries.index + +# значения выводятся в формате массива Numpy +countries.values + +# выведем описание индекса датафрейма через атрибут axes[0] +countries.axes[0] + +# axes[1] выводит названия столбцов +countries.axes[1] + +countries.ndim, countries.shape, countries.size + +# + +# Атрибут dtypes выдает тип данных каждого столбца. + +countries.dtypes + +# + +# Кроме того, с помощью метода .memory_usage() мы можем посмотреть +# объем занимаемой памяти по столбцам в байтах. + +countries.memory_usage() + +# + +# Индекс датафрейма +# Присвоение индекса + +# создадим список с кодами стран +custom_index = ["CN", "VN", "GB", "RU", "AR", "BO", "ZA"] + +# создадим датафрейм из словаря и передадим список через параметр index +countries = pd.DataFrame(countries_dict, index=custom_index) + +# посмотрим на результат +countries + +# + +# Этот индекс можно сбросить с помощью метода .reset_index(). + +# при этом параметр inplace = True сделает изменения постоянными +countries.reset_index(inplace=True) +countries + +# + +# Как мы видим, прошлый индекс стал отдельным столбцом. +# Снова сделаем этот столбец индексом +# с помощью метода .set_index(). + +# передадим методу название столбца, который хотим сделать индексом +countries.set_index("index", inplace=True) +countries + +# + +# Еще раз сбросим индекс, но на этот раз не будем делать его отдельным +# столбцом. Для этого передадим методу .reset_index() параметр drop = True. + +countries.reset_index(drop=True, inplace=True) +countries + +# + +# Собственный индекс можно создать, просто поместив +# новые значения в атрибут index. + + +countries.index = custom_index # type: ignore[assignment] +countries + +# + +# Многоуровневый индекс + +# Многоуровневый (MultiIndex) или иерархический (hierarchical) индекс +# позволяет задать несколько уровней (levels) для индексации строк или столбцов + +# создадим список из кортежей с названием континента и кодом страны +rows = [ + ("Asia", "CN"), + ("Asia", "VN"), + ("Europe", "GB"), + ("Europe", "RU"), + ("S. America", "AR"), + ("S. America", "BO"), + ("Africa", "ZA"), +] + +# + +# в столбцах название страны и столицы мы объединим в категорию names +# а размер населения, площадь и выход к морю в data + +cols = [ + ("names", "country"), + ("names", "capital"), + ("data", "population"), + ("data", "area"), + ("data", "sea"), +] + +# + +# Теперь создадим иерархический индекс для строк и столбцов +# с помощью функции pd.MultiIndex.from_tuples(). + +# создадим многоуровневый индекс для строк +# индексам присвоим названия через names = ['region', 'code'] +custom_multindex = pd.MultiIndex.from_tuples(rows, names=["region", "code"]) + +# сделаем то же самое для столбцов +custom_multicols = pd.MultiIndex.from_tuples(cols) + +# + +countries.index = custom_multindex +countries.columns = custom_multicols + +countries + +# + +# вернемся к обычному индексу и названиям столбцов +custom_cols = ["country", "capital", "population", "area", "sea"] + +countries.index = custom_index # type: ignore[assignment] +countries.columns = custom_cols # type: ignore[assignment] + +countries + +# + +# Преобразование в другие форматы +# Получившийся датафрейм можно преобразовать в словарь с помощью метода .to_dict(). + +print(countries.to_dict()) + +# + +# Аналогично, метод .to_numpy() преобразовывает данные в массив Numpy. + +countries.to_numpy() +# - + +# по умолчанию, индекс также станет частью .csv файла +# параметр index = False позволит этого избежать +countries.to_csv("countries.csv", index=False) + +# + +# Метод .to_list() позволяет преобразовать объект Series +# (столбец датафрейма) в список. + +print(countries.country.to_list()) +# - + +# Создание Series +country_list = [ + "China", + "South Africa", + "United Kingdom", + "Russia", + "Argentina", + "Vietnam", + "Australia", +] + +country_series = pd.Series(country_list) +country_series + +# например, выведем первый элемент +country_series[0] + +# + +# Создание Series из словаря + +country_dict = { + "CN": "China", + "ZA": "South Africa", + "GB": "United Kingdom", + "RU": "Russia", + "AR": "Argentina", + "VN": "Vietnam", + "AU": "Australia", +} +# - + +country_series = pd.Series(country_dict) +country_series + +country_series["AU"] + +# + +# Доступ к строкам, столбцам и элементам +# Циклы в датафрейме +# Цикл for позволяет получить доступ к названиям столбцов датафрейма. + +for column in countries: + print(column) + +# + +# Метод .iterrows() возвращает индекс строки и ее содержимое в формате Series. + +# прервем цикл после первой итерации с помощью break +for index, row in countries.iterrows(): + print(index) + print(row) + print("...") + print(type(row)) + break + +# + +# Получить доступ к элементам одной строки можно +# по индексу объекта Series +# (то есть по названиям столбцов исходного датафрейма). + +for _, row in countries.iterrows(): + # например, сформируем вот такое предложение + print(row["capital"] + " is the capital of " + row["country"]) + break + +# + +# Доступ к столбцам + +# выведем столбец capital датафрейма countries +countries["capital"] +# - + +# однако в этом случае название не должно содержать пробелов +countries.capital + +# Как уже было сказано, отдельные столбцы в датафрейме имеют тип данных Series. +type(countries.capital) + +# + +# Для того чтобы получить доступ к столбцам и +# при этом на выходе сформировать датафрейм, +# необходимо использовать двойные скобки. + +# логика здесь в том, что внутрениие скобки - это список, +# внешние - оператор индексации +countries[["capital"]] +# - + +countries[["capital", "area"]] + +# + +# Доступ к столбцам можно также получить с помощью метода +# .filter(), передав параметру items список с +# необходимыми нам столбцами. + +countries.filter(items=["capital", "population"]) + +# + +# Доступ к строкам + +# выведем строки со второй по пятую (не включительно) +countries[1:5] + +# + +# Методы .loc[] и .iloc[] +# Метод .loc[] позволяет получить доступ к строкам и столбцам через +# их названия (label-based location) +# - + +# для этого передадим методу .loc[] два списка: +# с индексами строк и названиями столбцов +countries.loc[["CN", "RU", "VN"], ["capital", "population", "area"]] + +# + +# Через двоеточие, как и в Numpy, мы можем вывести +# все строки или все столбцы датафрейма + + +# например, выведем все строки датафрейма +countries.loc[:, ["capital", "population", "area"]] + +# + +# Метод .loc[] также поддерживает значения Boolean + + +# например, выведем все строки и только последний столбец, +# передав список соответствующих логических значений +countries.loc[:, [False, False, False, False, True]] + +# + +# Метод .get_loc() позволяет узнать порядковый номер +# (начиная с нуля) строки или столбца по их индексу +# и названию соответственно. + +# выведем номер строки с индексом RU +countries.index.get_loc("RU") +# - + +countries.columns.get_loc("country") + +# + +# Метод .iloc[] +# Метод .iloc[] действует примерно также, как и .loc[], +# с тем отличием, что он основывается на числовом индексе +# (integer-based location). + +# теперь в списки мы передаем номера строк и столбцов, +# нумерация начинается с нуля +countries.iloc[[0, 3, 5], [0, 1, 2]] +# - + +# выведем первые три строки и последние два столбца +countries.iloc[:3, -2:] + +# + +# К столбцам удобно обращаться по их названиям, к строкам — +# по порядковому номеру (числовому индексу). + +# вначале передадим названия столбцов в двойных скобках, +# затем номера строк через метод .iloc[] +countries[["population", "area"]].iloc[[0, 3]] + +# + +# Многоуровневый индекс и методы .loc[] и .iloc[] +# вновь создадим датафрейм с многоуровневым индексом по строкам и столбцам +countries.index = custom_multindex +countries.columns = custom_multicols + +countries + +# + +# Для доступа к первой строке передадим методу +# .loc[] соответствующий двойной индекс. + +countries.loc["Asia", "CN"] +# - + +# выведем первую строку и столбцы с числовыми данными +countries.loc[ + ("Asia", "CN"), [("data", "population"), ("data", "area"), ("data", "sea")] +] + +# + +# Доступ к строкам можно получить, указав внутри кортежа +# название региона и список с кодами стран. + +countries.loc[("Asia", ["CN", "VN"]), :] + +# + +# Внутри кортежа можно указать только регион. В этом случае +# мы получим все находящиеся в нем страны. + +countries.loc[("Asia"), :] + +# + +# Аналогичным образом мы можем получить доступ к столбцам. + +countries.loc[:, [("names", "country"), ("data", "population")]] + +# + +# Метод .iloc[], при этом, игнорирует структуру иерархического индекса +# и использует простой числовой индекс. + +# получим доступ к четвертой строке и третьему, четвертому и пятому столбцам +countries.iloc[3, [2, 3, 4]] + +# + +# Метод .xs() +# Метод .xs() (от англ. cross-section, срез) позволяет получить доступ +# к определенному уровню иерархического индекса. Начнем со строк. + +# выберем Европу из уровня region +# axis = 0 указывает, что мы берем строки +countries.xs("Europe", level="region", axis=0) +# - + +# levels указывает, на каких уровнях искать названия столбцов +# параметр axis = 1 говорит о том, что мы имеем дело со столбцами +countries.xs(("names", "country"), axis=1) + +# + +# Кроме того, мы можем соединить два метода .xs() и одновременно +# получить доступ и к строкам, и к столбцам. + +# в данном случае мы можем не указывать level, потому что +# Europe и names находятся во внешних индексах, +# которые в level указаны по умолчанию +countries_xs = countries.xs("Europe", axis=0).loc[:, "names"] +countries_xs + +# + +# Вернем датафрейму одноуровневый индекс. + +# обновим атрибуты index и columns +countries.index = custom_index # type: ignore[assignment] +countries.columns = custom_cols # type: ignore[assignment] + +# посмотрим на исходный датафрейм +countries + +# + +# Метод .at[] +# Метод .at[] подходит для извлечения или записи одного значения датафрейма. + +countries.at["CN", "capital"] +# - + +# Фильтры +# создадим логическую маску для стран с населением больше миллиарда человек +countries.population > 1000 + +# применим логическую маску к исходному датафрейму +countries[countries.population > 1000] + +# отфильтруем датафрейм по критериям численности населения и площади +countries[(countries.population > 50) & (countries.area < 2)] + +# + +# вначале создаем нужные нам маски +population_mask = countries.population > 70 +area_mask = countries.population < 50 + +# затем объединяем их по необходимым условиям (в данном случае ИЛИ) +mask = population_mask | area_mask +# и применяем маску к исходному датафрейму +countries[mask] + +# + +# Метод .query() +# Метод .query() позволяет задавать условие фильтрации «своими словами». + +# например, выберем страны с населением более 50 млн. человек И +# площадью менее двух млн. кв. километров +countries.query("population > 50 and area < 2") +# - + +# выведем данные по Великобритании +countries.query("country == 'United Kingdom'") + +# + +# С помощью метода .isin() мы можем проверить наличие нескольких значений +# в определенном столбце, а затем использовать результат +# в качестве логической маски. + +# найдем строки, в которых в столбце capital присутствуют следующие значения +keyword_list = ["Beijing", "Moscow", "Hanoi"] + +countries_print = countries[countries.capital.isin(keyword_list)] +print(countries_print) +# - + +# Похожим образом можно использовать метод .startswith(). +# например, для нахождения стран, НЕ начинающихся с буквы "A" +countries_start = countries[~countries.country.str.startswith("A")] +countries_start + +# например, возьмем три строки с наибольшими значением по столбцу population +countries.nlargest(3, "population") + +# + +# Метод .argmax() выводит индекс строки, в которой в +# определенном столбце содержится наибольшее значение. + +# например, предположим, что мы хотим найти индекс страны с наибольшей площадью +countries_max = countries.area.argmax() +countries_max + +# + +# Посмотрим, какой стране соответствует этот индекс. + +# напомню, что двойные скобки позволяют вывести DataFrame, а не Series +countries_area = countries.iloc[[countries.area.argmax()]] +countries_area + +# + +# Вспомним, что в метод .loc[] можно передать тип данных Boolean. +# Используем это свойство для создания фильтра. + +# выведем страны с населением более 90 млн. человек +countries.loc[countries.population > 90, :] + +# + +# Метод .filter(), если использовать параметр like, +# ищет совпадения с искомой фразой в индексе +# (если axis = 0) или названии столбцов (если axis = 1). + +# найдем строки, в которых в индексе есть буквосочетание "ZA" +countries.filter(like="ZA", axis=0) +# - + +# Сортировка +# выполним сортировку по столбцу population, не сохраняя изменений, +# в возрастающем порядке (значение по умолчанию) +countries.sort_values(by="population", inplace=False, ascending=True) + +# теперь отсортируем по двум столбцам в нисходящем порядке +countries.sort_values(by=["area", "population"], inplace=False, ascending=False) + +# + +# Кроме того, можно отсортировать строки по индексу с помощью метода .sort_index(). + +countries.sort_index() diff --git a/python/makarov/chapter_10_numpy.ipynb b/python/makarov/chapter_10_numpy.ipynb new file mode 100644 index 00000000..256be33b --- /dev/null +++ b/python/makarov/chapter_10_numpy.ipynb @@ -0,0 +1,3706 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "45c25e38", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Массив Numpy.'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"\"\"Массив Numpy.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "47b4f4d0", + "metadata": {}, + "source": [ + "# Как создать массив Numpy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d71d15f", + "metadata": {}, + "outputs": [], + "source": [ + "# импортируем библиотеку matplotlib\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# библиотеку Numpy принято сокращать как np\n", + "import numpy as np\n", + "\n", + "# импортируем функцию csr_matrix()\n", + "from scipy.sparse import csr_matrix" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d901996b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Как создать массив Numpy\n", + "# Функция np.array()\n", + "arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n", + "arr" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5109ce3a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# или кортежа\n", + "arr = np.array((0, 1, 2, 3, 4, 5, 6, 7, 8, 9))\n", + "arr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a41ba4a5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Функция np.arange()\n", + "arr = np.arange(10)\n", + "arr" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "7d40e17d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2 4 6 8]\n" + ] + } + ], + "source": [ + "# зададим нижнюю и верхнюю границу и шаг\n", + "arr = np.arange(2, 10, 2)\n", + "print(arr)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d1e762a", + "metadata": {}, + "outputs": [], + "source": [ + "# Отличие range() от функции np.arange() заключается в том,\n", + "# что первая не допускает использования типа float.\n", + "\n", + "# создадим список с помощью функций range() и list()\n", + "# list(range(2, 5.5, 0.5))\n", + "# TypeError: 'float' object cannot be interpreted as an integer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18109d47", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]\n", + "float64\n" + ] + } + ], + "source": [ + "# Тип данных элементов массива\n", + "# создадим массив с элементами типа float\n", + "arr_f = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], float)\n", + "\n", + "print(arr_f)\n", + "\n", + "# тип данных можно посмотреть через атрибут dtype\n", + "print(arr_f.dtype)" + ] + }, + { + "cell_type": "markdown", + "id": "dba8f7f8", + "metadata": {}, + "source": [ + "# Свойства (атрибуты) массива" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "2a6e8d83", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2, 4, 6, 8])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Возьмем массив, который мы создали выше.\n", + "\n", + "arr" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "5dc42429", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# ndim позволяет узнать количество измерений\n", + "arr.ndim" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "a861e25c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(4,)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# shape выводит количество элементов в каждом измерении\n", + "arr.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "c57b11fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# общее количество элементов во всех измерениях\n", + "arr.size" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "6203a6b1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dtype('int64')" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# в нашем случае - это целое число длиной 64 бита\n", + "arr.dtype" + ] + }, + { + "cell_type": "markdown", + "id": "156e1176", + "metadata": {}, + "source": [ + "# Измерения массива" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5b96457", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array(42)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Массив с нулевой размерностью\n", + "\n", + "arr_0d = np.array(42)\n", + "arr_0d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "495184b1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "()\n", + "1\n" + ] + } + ], + "source": [ + "# выведем измерения, элементы в каждом из них\n", + "# и общее количество элементов\n", + "print(arr_0d.ndim)\n", + "print(arr_0d.shape)\n", + "print(arr_0d.size)\n", + "\n", + "# Атрибут shape показывает отсутствие размерности,\n", + "# а size указывает на один элемент в массиве." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cee71ae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Одномерный массив (вектор)\n", + "\n", + "# Вложив несколько массивов с нулевой размерностью\n", + "# в квадратные скобки, мы получим одномерный\n", + "# массив или вектор.\n", + "\n", + "arr_1d = np.array([1, 2, 3])\n", + "arr_1d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb437f16", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "(3,)\n", + "3\n" + ] + } + ], + "source": [ + "# снова воспользуемся атрибутами массива\n", + "print(arr_1d.ndim)\n", + "print(arr_1d.shape)\n", + "print(arr_1d.size)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f2de4f4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3],\n", + " [4, 5, 6]])" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Двумерный массив (матрица)\n", + "\n", + "# Поместив во вторые квадратные скобки, например,\n", + "# два одномерных массива, мы получим двумерный\n", + "# массив или матрицу.\n", + "\n", + "# с точки зрения синтаксиса - это просто вложенные списки\n", + "arr_2d = np.array([[1, 2, 3], [4, 5, 6]])\n", + "arr_2d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7c8ff6e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n", + "(2, 3)\n", + "6\n" + ] + } + ], + "source": [ + "# Посмотрим на свойства.\n", + "\n", + "print(arr_2d.ndim)\n", + "print(arr_2d.shape)\n", + "print(arr_2d.size)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "229df315", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1]\n", + " [2]\n", + " [3]]\n", + "(3, 1)\n" + ] + } + ], + "source": [ + "# точки зрения Numpy матрица с одной строкой\n", + "# или одним столбцом — это разные объекты.\n", + "# Начнем с матрицы, которая имеет три\n", + "# вектора по одному элементу.\n", + "\n", + "column = np.array([[1], [2], [3]])\n", + "print(column)\n", + "# посмотрим на размерность\n", + "print(column.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ccae89c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1 2 3]]\n" + ] + }, + { + "data": { + "text/plain": [ + "(1, 3)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Теперь наоборот, создадим матрицу с одной строкой,\n", + "# в которой три элемента.\n", + "\n", + "row = np.array([[1, 2, 3]])\n", + "print(row)\n", + "# размерность будет иной\n", + "row.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0de5c21c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0, 1, 2],\n", + " [ 3, 4, 5]],\n", + "\n", + " [[ 6, 7, 8],\n", + " [ 9, 10, 11]]])" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Трехмерный массив\n", + "\n", + "# При этом, вместо того чтобы вручную прописывать все 12 значений,\n", + "# мы последовательно воспользуемся функцией\n", + "# np.arange() и методом np.reshape().\n", + "# np.reshape() распределит элементы по измерениям\n", + "\n", + "arr_3d = np.arange(12).reshape(2, 2, 3)\n", + "arr_3d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d103b52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n", + "(2, 2, 3)\n", + "12\n" + ] + } + ], + "source": [ + "# выведем атрибуты\n", + "print(arr_3d.ndim)\n", + "print(arr_3d.shape)\n", + "print(arr_3d.size)\n", + "\n", + "# атрибут shape сначала выводит размерность внешнего измерения,\n", + "# в нем две матрицы. Далее в каждую матрицу вложены\n", + "# два одномерных вектора. В каждом векторе по три элемента." + ] + }, + { + "cell_type": "markdown", + "id": "792faf2a", + "metadata": {}, + "source": [ + "# Другие способы создания массива" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c89d818", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0., 0., 0., 0., 0.])" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Массив из нулей\n", + "# функция np.zeros()\n", + "# ей мы можем передать одно значение\n", + "# для создания одномерного массива,\n", + "# заполненного нулями\n", + "np.zeros(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "312ab544", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0., 0., 0.],\n", + " [0., 0., 0.]])" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# или кортеж из чисел для указания количества\n", + "# нулей в каждом измерении\n", + "np.zeros((2, 3))" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "e9835aaf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[1., 1., 1.],\n", + " [1., 1., 1.]],\n", + "\n", + " [[1., 1., 1.],\n", + " [1., 1., 1.]]])" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Массив из единиц\n", + "# Функция np.ones()\n", + "\n", + "# создадим трехмерный массив\n", + "np.ones((2, 2, 3))" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "c7585c16", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[4, 4, 4],\n", + " [4, 4, 4]])" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Массив, заполненный заданным значением\n", + "\n", + "# создадим матрицу 2 х 3 и заполним ее цифрой четыре\n", + "np.full((2, 3), 4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cbe44173", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[6.23042070e-307, 4.67296746e-307],\n", + " [1.69121096e-306, 2.55895886e-307],\n", + " [9.45763339e-308, 4.22787460e-307]])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Пустой массив Numpy\n", + "# функция np.empty() возвращает массив заданной размерности,\n", + "# но без инициализации его значений\n", + "\n", + "# создадим пустую матрицу 3 х 2\n", + "np.empty((3, 2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b172c42c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3],\n", + " [4, 5, 6]])" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Функции np.zeros_like(), np.ones_like(),\n", + "# np.full_like(), np.empty_like()\n", + "\n", + "# создадим массив 2 x 3 с числами от 1 до 6\n", + "a_var = np.arange(1, 7).reshape(2, 3)\n", + "a_var" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54d27de2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0, 0, 0],\n", + " [0, 0, 0]])" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# превратим его в массив с нулями\n", + "np.zeros_like(a_var)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db2caf19", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 1, 1],\n", + " [1, 1, 1]])" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# единицами\n", + "np.ones_like(a_var)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8e618c9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 2, 2],\n", + " [2, 2, 2]])" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# двойками\n", + "np.full_like(a_var, 2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d46070a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[25895968444448860, 23925768161198147, 32370111954616435],\n", + " [19985036884181084, 13792815028961357, 23362719424184366]])" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# и пустыми значениями\n", + "np.empty_like(a_var)" + ] + }, + { + "cell_type": "markdown", + "id": "1ce2218c", + "metadata": {}, + "source": [ + "# Функция np.linspace()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac6036bc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Функция np.linspace() позволяет указать диапазон начального\n", + "# и конечного значений, а также количество равноудаленных\n", + "# точек внутри этого диапазона (включая начальное и конечное значения).\n", + "\n", + "# создадим диапазон от 0 до 0,9 и\n", + "# разделим его на десять точек, включая 0 и 0,9\n", + "np.linspace(0, 0.9, 10)" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "4ad48302", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# с функцией np.arange мы точно знаем, где будут расположены точки\n", + "np.arange(0, 1, 0.1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "045cda5b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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O3bsOOBjN954o3r27KkHHniAJSMtgDWzaFLWH9Efio6ONm+teqUEWWcEWWtGtkOHDmadrDWjWy8lJcXWtuHQrKoh1kystUbdCrXW7mmrrJP1xxTOWFowadIkMytr1Zrds2eP+XhcXJyp9QzfZ43bwWuco6KilJCQQJBtIYRZAA2sOpfWpi/rOvjFhzWDBN9zxRVXmBcdjzzyiAlCVjmgGTNm/GhTGHyTVfbOYr2I/KFXX33VbKYFcHQsMwAAAIBjsasDAAAAjkWYBQAAgGMRZgEAAOBYhFkAAAA4FmEWAAAAjkWYBQAAgGMRZgEAAOBYhFkAAAA4FmEWAAAAjkWYBQAAgGMRZgEAAOBYhFkAcCCPx6NzzjlHLpdLU6ZM+dHnJkyYcMjPAYC/cXms33oAAMfJyclRv379VFlZqRUrVqhDhw7m40899ZTuu+8+3XDDDXr11VftbiYANCvCLAA42IwZM8wM7ciRIzV37lytWrVKw4cPN8F26dKlio6OtruJANCsWGYAAA529tln6+6779b8+fP14IMP6qqrrjLLDN555x2CLICAwMwsADictcxgxIgRWr58uXn/z3/+s+6//367mwUALYKZWQBwuPDwcLPhyxIREaGf/exndjcJAFoMYRYAHG7RokV68sknlZCQoIqKCt122212NwkAWgxhFgAcrLi4WFdffbVCQkL09ddf65JLLtG7776rV155xe6mAUCLYM0sADjYtddeq7feekv/+Mc/dPvttys/P1/9+/fX/v37TTWD7t27291EAGhWhFkAcCgrxFph9vzzz9fHH3/c8HGrRNcZZ5yhgQMHasGCBQoNDbW1nQDQnFhmAAAOtG3bNjMTm5qa+qMlBaNGjdJDDz2kJUuW6OGHH7atjQDQEpiZBQAAgGMxMwsAAADHIswCAADAsQizAAAAcCzCLAAAAByLMAsAAADHIswCAADAsQizAAAAcCzCLAAAAByLMAsAAADHIswCAADAsQizAAAAcCzCLAAAAByLMAsAAAA51f8H+ohoPvio0N8AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Функцию np.linspace() удобно использовать\n", + "# для построения графиков функций.\n", + "\n", + "\n", + "# зададим размер графика в дюймах\n", + "plt.figure(figsize=(8, 6))\n", + "\n", + "# зададим интервал, например, от -5 до 5 и\n", + "# сформируем на нем 5000 точек\n", + "# это будут наши координаты по оси x\n", + "x_var = np.linspace(-5, 5, 5000)\n", + "\n", + "# по оси y отложим квадрат этих точек\n", + "y_var = x_var**2\n", + "\n", + "# создадим сетку\n", + "plt.grid()\n", + "\n", + "# выведем кривую и подписи на графике\n", + "plt.plot(x_var, y_var)\n", + "plt.xlabel(\"x\", fontsize=14)\n", + "plt.ylabel(\"y\", fontsize=14)\n", + "\n", + "# результатом будет парабола\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a674887f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-5. , -4.9979996, -4.9959992, -4.9939988, -4.9919984,\n", + " -4.989998 , -4.9879976, -4.9859972, -4.9839968, -4.9819964])" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# в качестве примера выведем первые десять точек,\n", + "# созданные функцией np.linspace()\n", + "x_var[:10]" + ] + }, + { + "cell_type": "markdown", + "id": "db53be09", + "metadata": {}, + "source": [ + "# Функции np.random.rand() и np.random.randint()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7980942", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0.7636817 , 0.67260241, 0.24682851],\n", + " [0.85527626, 0.78757804, 0.23987816],\n", + " [0.80061534, 0.77608566, 0.3814508 ],\n", + " [0.3904952 , 0.08106927, 0.22281468]])" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Массивы можно также создавать с помощью функций,\n", + "# генерирующих псевдослучайные числа. В частности,\n", + "# функция np.random.rand() создает массив заданной\n", + "# размерности, заполненный числами от 0 до 1\n", + "# (единица в диапазон не входит).\n", + "\n", + "# создадим массив заданной размерности, заполненный числами\n", + "# в интервале [0, 1)\n", + "np.random.rand(4, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "5259a1b9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[-3, -2],\n", + " [-2, -2],\n", + " [ 2, -3]],\n", + "\n", + " [[ 2, -2],\n", + " [-3, 1],\n", + " [ 1, 2]]], dtype=int32)" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# создадим массив целых чисел в заданном диапазоне\n", + "# (верхняя граница в него не входит)\n", + "# и с заданной размерностью\n", + "\n", + "np.random.randint(-3, 3, size=(2, 3, 2))" + ] + }, + { + "cell_type": "markdown", + "id": "74b39036", + "metadata": {}, + "source": [ + "# Создание массива из функции" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be324d04", + "metadata": {}, + "outputs": [], + "source": [ + "# Помимо вышеупомянутых способов массив можно\n", + "# создавать с помощью собственных функций.\n", + "\n", + "\n", + "# создадим собственную функцию, которая принимает два числа\n", + "# и возводит первое число в степень второго\n", + "def power(i_var: int | float, j_var: int | float) -> int | float:\n", + " \"\"\"Возводит число в степень.\n", + "\n", + " Args:\n", + " i_var: Основание\n", + " j_var: Показатель степени\n", + "\n", + " Returns:\n", + " Результат i в степени j\n", + " \"\"\"\n", + " return pow(i_var, j_var)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "0802ac03", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1., 0., 0.],\n", + " [1., 1., 1.],\n", + " [1., 2., 4.]])" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# применим эту функцию к каждой ячейке массива 3 x 3\n", + "np.fromfunction(power, (3, 3))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "491859e9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ True, False, False],\n", + " [False, True, False],\n", + " [False, False, True]])" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# также можно передать lambda-функцию, которая\n", + "# проверяет равенство двух чисел\n", + "np.fromfunction(lambda i_var, j_var: i_var == j_var, (3, 3))" + ] + }, + { + "cell_type": "markdown", + "id": "7739fce7", + "metadata": {}, + "source": [ + "# Матрица csr и метод .toarray()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d99a15b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 0, 0, 1, 0, 0, 0],\n", + " [0, 0, 3, 0, 0, 2, 0],\n", + " [0, 0, 0, 1, 0, 0, 0]])" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Кроме этого в случае если данные хранятся в\n", + "# формате csr (сжатое хранения строкой,\n", + "# compressed sparse row), то мы можем\n", + "# преобразовать их обратно в массив Numpy с помощью метода .toarray().\n", + "\n", + "# создадим матрицу с преобладанием нулевых значений\n", + "null_arr = np.array(\n", + " [[2, 0, 0, 1, 0, 0, 0], [0, 0, 3, 0, 0, 2, 0], [0, 0, 0, 1, 0, 0, 0]]\n", + ")\n", + "null_arr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f53b484", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.float64(0.7619047619047619)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# посчитаем долю нулевых значений\n", + "null_frac = 1.0 - np.count_nonzero(null_arr) / null_arr.size\n", + "print(null_frac)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d9ff568", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: scipy in k:\\git_desktop_repository\\data-science-for-beginners-from-scratch-senatorov\\.venv\\lib\\site-packages (1.16.3)\n", + "Requirement already satisfied: numpy<2.6,>=1.25.2 in k:\\git_desktop_repository\\data-science-for-beginners-from-scratch-senatorov\\.venv\\lib\\site-packages (from scipy) (2.3.4)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "[notice] A new release of pip is available: 25.2 -> 25.3\n", + "[notice] To update, run: python.exe -m pip install --upgrade pip\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Coords\tValues\n", + " (0, 0)\t2\n", + " (0, 3)\t1\n", + " (1, 2)\t3\n", + " (1, 5)\t2\n", + " (2, 3)\t1\n" + ] + } + ], + "source": [ + "!{sys.executable} -m pip install scipy\n", + "# Преобразуем матрицу в формат csr.\n", + "\n", + "\n", + "# матрицу A удобно хранить в формате csr,\n", + "csr_arr = csr_matrix(null_arr)\n", + "\n", + "# где указывается положение ненулевого значения и само значение\n", + "print(csr_arr)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eeb853d2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 0, 0, 1, 0, 0, 0],\n", + " [0, 0, 3, 0, 0, 2, 0],\n", + " [0, 0, 0, 1, 0, 0, 0]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# восстановить массив Numpy можно с помощью метода .toarray()\n", + "rest_arr = csr_arr.toarray()\n", + "rest_arr" + ] + }, + { + "cell_type": "markdown", + "id": "111fc301", + "metadata": {}, + "source": [ + "# Индексы и срезы" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c4fae3f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3],\n", + " [4, 5, 6]])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# индексы\n", + "# рассмотрим двумерный массив\n", + "a_2d = np.array([[1, 2, 3], [4, 5, 6]])\n", + "a_2d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dbf772a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(2, 3)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# первое значение атрибута shape относится к\n", + "# внешнему измерению\n", + "# второе - к внутреннему\n", + "a_2d.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7046633d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем первый элемент внешнего измерения\n", + "a_2d[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2fef29b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(1)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# второй индекс [0] позволяет обратиться\n", + "# к элементам внутреннего измерения\n", + "a_2d[0][0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d847b6e2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(6)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем значение шесть\n", + "a_2d[1][2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2785cb91", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3, 4, 5, 6, 7, 8])" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Срез массива\n", + "# начнем с одномерного массива\n", + "b_slice = np.array([1, 2, 3, 4, 5, 6, 7, 8])\n", + "b_slice" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "718dcf52", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2, 4, 6])" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# возьмем каждый второй элемент\n", + "# в интервале с 1-го по 6-й индекс\n", + "b_slice[1:6:2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2745831", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3, 4],\n", + " [5, 6, 7, 8]])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# теперь возьмем двумерный массив\n", + "c_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])\n", + "c_2d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94cc00a8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2])" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# сделаем срез из первой строки и первых двух столбцов\n", + "c_2d[0, :2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a3e9982", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2, 6])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# а теперь возьмем обе строки во втором столбце\n", + "c_2d[:, 1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b9b42e64", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(1)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем элемент в первой строке и первом столбце\n", + "c_2d[0, 0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "167532b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(8)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем элемент в последней строке и последнем столбце\n", + "c_2d[-1, -1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c350fe6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([5, 7])" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# возьмем всю вторую строку и каждый второй столбец\n", + "c_2d[1, ::2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a234003e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0, 1],\n", + " [ 2, 3]],\n", + "\n", + " [[ 4, 5],\n", + " [ 6, 7]],\n", + "\n", + " [[ 8, 9],\n", + " [10, 11]],\n", + "\n", + " [[12, 13],\n", + " [14, 15]]])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Массив 3D\n", + "# создадим трехмерный массив\n", + "d_3d = np.arange(16).reshape(4, 2, -1)\n", + "d_3d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4487131b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(10)" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем значение 10\n", + "d_3d[2][1][0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d09cb90", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[10, 11],\n", + " [14, 15]])" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем третью и четвертую матрицу [2:]\n", + "# и в них вторую строку [1] и все столбцы [:]\n", + "d_3d[2:, 1, :]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a66edcd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[0, 1],\n", + " [2, 3]],\n", + "\n", + " [[4, 5],\n", + " [6, 7]]])" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем первые две матрицы массива\n", + "d_3d[:2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5cc5040", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 1],\n", + " [ 4, 5],\n", + " [ 8, 9],\n", + " [12, 13]])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем первые строки каждой матрицы\n", + "d_3d[:, 0, :]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f4a87a7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2],\n", + " [3, 4]])" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Оси массива\n", + "# Массив 2D\n", + "\n", + "# обратимся к двумерному массиву\n", + "arr_2d_axis = np.array([[1, 2], [3, 4]])\n", + "arr_2d_axis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3a3f113", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(2, 2)" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "arr_2d_axis.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa3006f8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([4, 6])" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# найдем сумму по столбцам (вдоль оси 0)\n", + "np.sum(arr_2d_axis, axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8df7551", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([3, 7])" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# и сумму по строкам (вдоль оси 1)\n", + "np.sum(arr_2d_axis, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb7b1cf0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(10)" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# если передать кортеж с указанием обеих осей (0, 1)\n", + "# сумма будет рассчитана вначале вдоль оси 0, затем вдоль оси 1\n", + "np.sum(arr_2d_axis, axis=(0, 1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7c7c422", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(10)" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# если ничего не указывать, сумма также будет\n", + "# рассчитана по всем элементам массива\n", + "np.sum(arr_2d_axis)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51de4d52", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(10)" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# в этом случае под капотом стоит значение по\n", + "# умолчанию axis = None\n", + "np.sum(arr_2d_axis, axis=None)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55b11e3a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([3, 7])" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# axis = -1 соответствует последней оси массива,\n", + "# в данном случае, axis = 1\n", + "np.sum(arr_2d_axis, axis=-1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4645c2df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([4, 6])" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# axis = -2 указывает на первую ось, то есть axis = 0\n", + "np.sum(arr_2d_axis, axis=-2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bafc277", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0, 1, 2],\n", + " [ 3, 4, 5]],\n", + "\n", + " [[ 6, 7, 8],\n", + " [ 9, 10, 11]]])" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Массив 3D\n", + "# обратимся к трехмерному массиву\n", + "arr_3d_axis = np.arange(12).reshape(2, 2, 3)\n", + "arr_3d_axis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31c936af", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 6, 8, 10],\n", + " [12, 14, 16]])" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# применим np.sum() с параметром axis = 0\n", + "np.sum(arr_3d_axis, axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c6a4d8f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0, 1, 2],\n", + " [3, 4, 5]])" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# теперь возьмем первую матрицу\n", + "arr_3d_axis[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12230433", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 6, 7, 8],\n", + " [ 9, 10, 11]])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# возьмем вторую матрицу\n", + "arr_3d_axis[1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "196ee80b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 6, 8, 10],\n", + " [12, 14, 16]])" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# и поэлементно сложим их\n", + "arr_3d_axis[0] + arr_3d_axis[1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "009af861", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 3, 5, 7],\n", + " [15, 17, 19]])" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Сложение вдоль второй оси (axis = 1)\n", + "# применим np.sum()\n", + "np.sum(arr_3d_axis, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd764b83", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([3, 5, 7])" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# сложим столбцы первой\n", + "arr_3d_axis[0][0] + arr_3d_axis[0][1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a758547d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([15, 17, 19])" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# и второй матрицы\n", + "arr_3d_axis[1][0] + arr_3d_axis[1][1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f65f3b4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 3, 12],\n", + " [21, 30]])" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Сложение вдоль третьей оси (axis = 2)\n", + "# применим np.sum()\n", + "np.sum(arr_3d_axis, axis=2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9cc1e14", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(3)" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# сложим элементы первой строки первой матрицы\n", + "arr_3d_axis[0][0][0] + arr_3d_axis[0][0][1] + arr_3d_axis[0][0][2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81933baf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(12)" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# второй строки первой матрицы\n", + "arr_3d_axis[0][1][0] + arr_3d_axis[0][1][1] + arr_3d_axis[0][1][2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f643ba9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(21)" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# первой строки второй матрицы\n", + "arr_3d_axis[1][0][0] + arr_3d_axis[1][0][1] + arr_3d_axis[1][0][2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "572094e7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(30)" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# второй строки второй матрицы\n", + "arr_3d_axis[1][1][0] + arr_3d_axis[1][1][1] + arr_3d_axis[1][1][2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52e8c97a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([18, 22, 26])" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Сложение вдоль первой и второй осей (axis = (0, 1))\n", + "# применим функцию np.sum()\n", + "np.sum(arr_3d_axis, axis=(0, 1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6549d7c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(66)" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Сложение вдоль всех трех осей (axis = (0, 1, 2))\n", + "np.sum(arr_3d_axis, axis=(0, 1, 2))" + ] + }, + { + "cell_type": "markdown", + "id": "11821b1c", + "metadata": {}, + "source": [ + "# Операции с массивами" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cabd989f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0, 1, 2],\n", + " [ 3, 4, 5]],\n", + "\n", + " [[ 6, 7, 8],\n", + " [ 9, 10, 11]]])" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Вновь возьмем трехмерный массив.\n", + "# Функция len()\n", + "arr_3d_axis = np.arange(12).reshape(2, 2, 3)\n", + "arr_3d_axis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21c6367d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(arr_3d_axis)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "307a0c59", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# чтобы вывести длину внутреннего измерения,\n", + "# т.е. вектора из трех элементов,\n", + "# нужно воспользоваться индексами\n", + "len(arr_3d_axis[0][0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08a14bfa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Вхождение элемента в массив\n", + "# проверим, входит ли значение 3 в массив arr_3D\n", + "3 in arr_3d_axis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "722004bc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# проверим, не входит ли значение 11 в массив arr_3D\n", + "11 not in arr_3d_axis" + ] + }, + { + "cell_type": "markdown", + "id": "f94df1fb", + "metadata": {}, + "source": [ + "# Распакова массива" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac79a0d3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9],\n", + " [10, 11, 12, 13, 14, 15, 16, 17, 18],\n", + " [19, 20, 21, 22, 23, 24, 25, 26, 27]])" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# возьмем матрицу из трех строк и девяти столбцов\n", + "a_unpack = np.arange(1, 28).reshape(3, 9)\n", + "a_unpack" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b857a21", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3, 4, 5, 6, 7, 8, 9])" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# каждую строку можно распаковать в отдельную переменную\n", + "x_unp, y_unp, z_unp = a_unpack\n", + "\n", + "# выведем первую переменную (строку)\n", + "x_unp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2634e0dd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "[np.int64(2), np.int64(3), np.int64(4), np.int64(5), np.int64(6), np.int64(7), np.int64(8)]\n", + "9\n" + ] + } + ], + "source": [ + "# теперь распакуем первый, последний и остальные элементы\n", + "# первой строки в отдельные переменные\n", + "x_sep, *y_sep, z_sep = a_unpack[0]\n", + "\n", + "# выведем каждую переменную\n", + "print(x_sep)\n", + "print(y_sep)\n", + "print(z_sep)" + ] + }, + { + "cell_type": "markdown", + "id": "9d80bfca", + "metadata": {}, + "source": [ + "# Изменение элементов массива" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b047bdb7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3],\n", + " [4, 5, 6]])" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# обратимся к двумерному массиву\n", + "arr_2d_index = np.array([[1, 2, 3], [4, 5, 6]])\n", + "arr_2d_index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77d4b0f5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 2, 3],\n", + " [4, 5, 6]])" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# заменим первый элемент первой строки по его индексу\n", + "arr_2d_index[0, 0] = 2\n", + "arr_2d_index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50861eed", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 1, 1],\n", + " [4, 5, 6]])" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# запишем значение 1 в первую строку\n", + "arr_2d_index[0] = 1\n", + "arr_2d_index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6345e623", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 1, 0],\n", + " [4, 5, 0]])" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# запишем 0 в третий столбец массива\n", + "arr_2d_index[:, 2] = 0\n", + "arr_2d_index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4bcf4f9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0, 1, 2],\n", + " [ 3, 4, 5]],\n", + "\n", + " [[ 6, 7, 8],\n", + " [ 9, 10, 11]]])" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# обратимся к трехмерному массиву\n", + "arr_3d_index = np.arange(12).reshape(2, 2, 3)\n", + "arr_3d_index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bce8b3cf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0, 1, 2],\n", + " [ 3, 4, 5]],\n", + "\n", + " [[ 6, 0, 8],\n", + " [ 9, 1, 11]]])" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выберем второй столбец второй матрицы и\n", + "# заменим значения столбца 7 и 10 на 0 и 1\n", + "arr_3d_index[1, :, 1] = [0, 1]\n", + "\n", + "# при такой операции размер среза должен совпадать\n", + "# с количеством передаваемых значений\n", + "arr_3d_index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2ba2e7a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[7, 7, 7],\n", + " [7, 7, 7]],\n", + "\n", + " [[7, 7, 7],\n", + " [7, 7, 7]]])" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# заменим все элементы массива на число семь\n", + "arr_3d_index.fill(7)\n", + "arr_3d_index" + ] + }, + { + "cell_type": "markdown", + "id": "5aaaea71", + "metadata": {}, + "source": [ + "# Сортировка массива и обратный порядок его элементов" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28cbbf54", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[4, 8, 2],\n", + " [2, 3, 1]])" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Функция np.sort()\n", + "# возьмем двумерный массив\n", + "a_sort = np.array([[4, 8, 2], [2, 3, 1]])\n", + "a_sort" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98401b77", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 4, 8],\n", + " [1, 2, 3]])" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# по умолчанию сортировка идет с параметром axis = -1 (последняя ось)\n", + "np.sort(a_sort)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b08e34ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 4, 8],\n", + " [1, 2, 3]])" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# для двумерного массива это ось 1\n", + "np.sort(a_sort, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a0a5c4a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 3, 1],\n", + " [4, 8, 2]])" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# посмотрим на сортировку по оси 0\n", + "np.sort(a_sort, axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f2afe5b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 2, 3, 4, 8])" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# axis = None вначале возвращает одномерный массив,\n", + "# а затем сортирует\n", + "np.sort(a_sort, axis=None)" + ] + }, + { + "cell_type": "markdown", + "id": "a6088a69", + "metadata": {}, + "source": [ + "# Обратный порядок элементов массива через оператор среза" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17b364a9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# оператор среза с параметром шага -1\n", + "# задает обратный порядок элементов массива\n", + "reverse_arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])[::-1]\n", + "print(reverse_arr)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7247dd6d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([7, 6, 5, 4])" + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# обратный порядок можно совмещать со срезами\n", + "reverse_arr_slice = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])[-3:3:-1]\n", + "print(reverse_arr_slice)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04212db3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[4, 8, 2],\n", + " [2, 3, 1],\n", + " [1, 7, 2]])" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# в двумерном массиве\n", + "a_rev = np.array([[4, 8, 2], [2, 3, 1], [1, 7, 2]])\n", + "a_rev" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b4ffe70", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 7, 1],\n", + " [1, 3, 2],\n", + " [2, 8, 4]])" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# можно задать обратный порядок по двум измерениям (axis = (0, 1))\n", + "a_rev[::-1, ::-1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0077d88d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 7, 2],\n", + " [2, 3, 1],\n", + " [4, 8, 2]])" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# обратный порядок по внешнему (axis = 0)\n", + "a_rev[::-1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a5d3f33", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 8, 4],\n", + " [1, 3, 2],\n", + " [2, 7, 1]])" + ] + }, + "execution_count": 77, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# и внутреннему измерению (axis = 1)\n", + "a_rev[:, ::-1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ac86331", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 7, 1],\n", + " [1, 3, 2],\n", + " [2, 8, 4]])" + ] + }, + "execution_count": 78, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Обратный порядок через функцию np.flip()\n", + "\n", + "# по умолчанию, задает обратный порядок по двум измерениям\n", + "# то же самое, что axis = (0, 1)\n", + "np.flip(a_rev)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "643a61e2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 7, 2],\n", + " [2, 3, 1],\n", + " [4, 8, 2]])" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# можно также задать обратный порядок по внешнему\n", + "np.flip(a_rev, axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "acd3c7c5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 8, 4],\n", + " [1, 3, 2],\n", + " [2, 7, 1]])" + ] + }, + "execution_count": 80, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# и внутреннему измерениям\n", + "np.flip(a_rev, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b9da004", + "metadata": {}, + "outputs": [], + "source": [ + "# для сортировки массива в убывающем порядке\n", + "a_rev = np.array([4, 2, 6, 1, 7, 3, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc42404e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([7, 6, 5, 4, 3, 2, 1])" + ] + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# мы можем последовательно применить np.sort() и оператор среза\n", + "sorted_desc = np.sort(a_rev)[::-1]\n", + "print(sorted_desc)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "341da21e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([4, 2, 6, 1, 7, 3, 5])" + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# исходный массив не изменился\n", + "a_rev" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da84de73", + "metadata": {}, + "outputs": [], + "source": [ + "# можно и обратном порядке, но уже с методом .sort()\n", + "a_rev[::-1].sort()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "728b4b89", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([7, 6, 5, 4, 3, 2, 1])" + ] + }, + "execution_count": 85, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# изменение стало постоянным\n", + "a_rev" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "037e7d02", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0, 1, 2],\n", + " [ 3, 4, 5]],\n", + "\n", + " [[ 6, 7, 8],\n", + " [ 9, 10, 11]]])" + ] + }, + "execution_count": 86, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Изменение размерности\n", + "# возьмем простой трехмерный массив\n", + "arr_3d_index = np.arange(12).reshape(2, 2, 3)\n", + "arr_3d_index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "af4a4a5d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "12" + ] + }, + "execution_count": 87, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# в нем 12 элементов\n", + "arr_3d_index.size" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7188792a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 1, 2, 3, 4, 5],\n", + " [ 6, 7, 8, 9, 10, 11]])" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# поменяем размерность при сохранении общего количества элементов\n", + "arr_2d_index = arr_3d_index.reshape(2, 6)\n", + "arr_2d_index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c16d477", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 1, 2, 3, 4, 5],\n", + " [ 6, 7, 8, 9, 10, 11],\n", + " [ 0, 1, 2, 3, 4, 5]])" + ] + }, + "execution_count": 89, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# функция np.resize() позволяет не сохранять\n", + "# прежнее количество элементов\n", + "# существующие элементы копируются в новые ячейки\n", + "np.resize(arr_2d_index, (3, 6))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5485e378", + "metadata": {}, + "outputs": [], + "source": [ + "# arr_2D ссылается на другой массив, поэтому\n", + "# сначала нужно сделать его копию\n", + "arr_2d_copy = arr_2d_index.copy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b95293b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 1, 2, 3, 4, 5],\n", + " [ 6, 7, 8, 9, 10, 11],\n", + " [ 0, 0, 0, 0, 0, 0],\n", + " [ 0, 0, 0, 0, 0, 0]])" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# метод .resize() сделает копию, изменит размерность\n", + "# и заполнит пропуски нулями\n", + "arr_2d_copy.resize(4, 6)\n", + "arr_2d_copy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dede71f3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])" + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# .flatten() переводит (\"вытягивает\") массив в одно измерение и\n", + "# создает копию исходного массива (как метод .copy())\n", + "arr_3d_index.flatten()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7f7d571", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])" + ] + }, + "execution_count": 93, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# .ravel() делает то же самое,\n", + "# но не создает копию исходного массива и поэтому быстрее чем .flatten()\n", + "arr_3d_index.ravel()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "231d00d5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(3,)" + ] + }, + "execution_count": 94, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# np.newaxis добавляет измерение в массиве\n", + "a_newaxis = np.array([1, 2, 3])\n", + "a_newaxis.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0620f27f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1 2 3]]\n", + "(1, 3)\n" + ] + } + ], + "source": [ + "# добавим первое измерение\n", + "b_newaxis = a_newaxis[np.newaxis, :]\n", + "\n", + "print(b_newaxis)\n", + "print(b_newaxis.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca944852", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1]\n", + " [2]\n", + " [3]]\n", + "(3, 1)\n" + ] + } + ], + "source": [ + "# добавим второе измерение\n", + "c_two_newaxis = a_newaxis[:, np.newaxis]\n", + "\n", + "print(c_two_newaxis)\n", + "print(c_two_newaxis.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e467ae75", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2],\n", + " [3, 4]])" + ] + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Функция np.expand_dims()\n", + "# возьмем двумерный массив\n", + "a_expand = np.array([[1, 2], [3, 4]])\n", + "a_expand" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36a8cb57", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[1, 2],\n", + " [3, 4]]])" + ] + }, + "execution_count": 98, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# добавим внешнее измерение\n", + "np.expand_dims(a_expand, axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa5647e4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[1, 2]],\n", + "\n", + " [[3, 4]]])" + ] + }, + "execution_count": 99, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# добавим измерение \"по середине\"\n", + "np.expand_dims(a_expand, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e4138d79", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[[0],\n", + " [1],\n", + " [2]],\n", + "\n", + " [[3],\n", + " [4],\n", + " [5]],\n", + "\n", + " [[6],\n", + " [7],\n", + " [8]]]])" + ] + }, + "execution_count": 100, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# возьмем массив 4D\n", + "# первое и последнее измерения содержат по одному элементу\n", + "arr_4d_resize = np.arange(9).reshape(1, 3, 3, 1)\n", + "arr_4d_resize" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a2761fd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0, 1, 2],\n", + " [3, 4, 5],\n", + " [6, 7, 8]])" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# удалим эти измерения с помощью функции np.squeeze()\n", + "np.squeeze(arr_4d_resize)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8519c47", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(3, 3)" + ] + }, + "execution_count": 102, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# новый массив имеет только два измерения\n", + "arr_squeeze = np.squeeze(arr_4d_resize).shape\n", + "\n", + "print(arr_squeeze)" + ] + }, + { + "cell_type": "markdown", + "id": "e9746413", + "metadata": {}, + "source": [ + "# Объединение массивов" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8bb761d2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0, 1],\n", + " [2, 3]])" + ] + }, + "execution_count": 103, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# создадим два квадратных массива\n", + "a_sq = np.arange(4).reshape(2, 2)\n", + "a_sq" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7b71985", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[4, 5],\n", + " [6, 7]])" + ] + }, + "execution_count": 104, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "b_sq = np.arange(4, 8).reshape(2, 2)\n", + "b_sq" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b4e10c4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0, 1],\n", + " [2, 3],\n", + " [4, 5],\n", + " [6, 7]])" + ] + }, + "execution_count": 105, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# объединим два массива вдоль оси 0 без\n", + "# (!) добавления нового измерения\n", + "np.concatenate((a_sq, b_sq), axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02e3cc84", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0, 1, 4, 5],\n", + " [2, 3, 6, 7]])" + ] + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# то же самое вдоль оси 1\n", + "np.concatenate((a_sq, b_sq), axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b54bfeae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[0, 1],\n", + " [2, 3]],\n", + "\n", + " [[4, 5],\n", + " [6, 7]]])" + ] + }, + "execution_count": 107, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Функция np.stack()\n", + "# здесь отличие в том, что мы добавляем новую ось (измерение)\n", + "np.stack((a_sq, b_sq), axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "484dacfe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[0, 1],\n", + " [4, 5]],\n", + "\n", + " [[2, 3],\n", + " [6, 7]]])" + ] + }, + "execution_count": 108, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# при axis = 1 мы объединяем первые и вторые строки двух массивов\n", + "np.stack((a_sq, b_sq), axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9fd2eff1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[0, 4],\n", + " [1, 5]],\n", + "\n", + " [[2, 6],\n", + " [3, 7]]])" + ] + }, + "execution_count": 109, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# при axis = 2 объединяются элементы с одинаковыми индексами\n", + "np.stack((a_sq, b_sq), axis=2)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_10_numpy.py b/python/makarov/chapter_10_numpy.py new file mode 100644 index 00000000..49207285 --- /dev/null +++ b/python/makarov/chapter_10_numpy.py @@ -0,0 +1,768 @@ +"""Массив Numpy.""" + +# # Как создать массив Numpy + +# + +# импортируем библиотеку matplotlib +import matplotlib.pyplot as plt + +# библиотеку Numpy принято сокращать как np +import numpy as np + +# импортируем функцию csr_matrix() +from scipy.sparse import csr_matrix + +# - + +# Как создать массив Numpy +# Функция np.array() +arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) +arr + +# или кортежа +arr = np.array((0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) +arr + +# Функция np.arange() +arr = np.arange(10) +arr + +# зададим нижнюю и верхнюю границу и шаг +arr = np.arange(2, 10, 2) +print(arr) + +# + +# Отличие range() от функции np.arange() заключается в том, +# что первая не допускает использования типа float. + +# создадим список с помощью функций range() и list() +# list(range(2, 5.5, 0.5)) +# TypeError: 'float' object cannot be interpreted as an integer + +# + +# Тип данных элементов массива +# создадим массив с элементами типа float +arr_f = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], float) + +print(arr_f) + +# тип данных можно посмотреть через атрибут dtype +print(arr_f.dtype) +# - + +# # Свойства (атрибуты) массива + +# + +# Возьмем массив, который мы создали выше. + +arr +# - + +# ndim позволяет узнать количество измерений +arr.ndim + +# shape выводит количество элементов в каждом измерении +arr.shape + +# общее количество элементов во всех измерениях +arr.size + +# в нашем случае - это целое число длиной 64 бита +arr.dtype + +# # Измерения массива + +# + +# Массив с нулевой размерностью + +arr_0d = np.array(42) +arr_0d + +# + +# выведем измерения, элементы в каждом из них +# и общее количество элементов +print(arr_0d.ndim) +print(arr_0d.shape) +print(arr_0d.size) + +# Атрибут shape показывает отсутствие размерности, +# а size указывает на один элемент в массиве. + +# + +# Одномерный массив (вектор) + +# Вложив несколько массивов с нулевой размерностью +# в квадратные скобки, мы получим одномерный +# массив или вектор. + +arr_1d = np.array([1, 2, 3]) +arr_1d +# - + +# снова воспользуемся атрибутами массива +print(arr_1d.ndim) +print(arr_1d.shape) +print(arr_1d.size) + +# + +# Двумерный массив (матрица) + +# Поместив во вторые квадратные скобки, например, +# два одномерных массива, мы получим двумерный +# массив или матрицу. + +# с точки зрения синтаксиса - это просто вложенные списки +arr_2d = np.array([[1, 2, 3], [4, 5, 6]]) +arr_2d + +# + +# Посмотрим на свойства. + +print(arr_2d.ndim) +print(arr_2d.shape) +print(arr_2d.size) + +# + +# точки зрения Numpy матрица с одной строкой +# или одним столбцом — это разные объекты. +# Начнем с матрицы, которая имеет три +# вектора по одному элементу. + +column = np.array([[1], [2], [3]]) +print(column) +# посмотрим на размерность +print(column.shape) + +# + +# Теперь наоборот, создадим матрицу с одной строкой, +# в которой три элемента. + +row = np.array([[1, 2, 3]]) +print(row) +# размерность будет иной +row.shape + +# + +# Трехмерный массив + +# При этом, вместо того чтобы вручную прописывать все 12 значений, +# мы последовательно воспользуемся функцией +# np.arange() и методом np.reshape(). +# np.reshape() распределит элементы по измерениям + +arr_3d = np.arange(12).reshape(2, 2, 3) +arr_3d + +# + +# выведем атрибуты +print(arr_3d.ndim) +print(arr_3d.shape) +print(arr_3d.size) + +# атрибут shape сначала выводит размерность внешнего измерения, +# в нем две матрицы. Далее в каждую матрицу вложены +# два одномерных вектора. В каждом векторе по три элемента. +# - + +# # Другие способы создания массива + +# Массив из нулей +# функция np.zeros() +# ей мы можем передать одно значение +# для создания одномерного массива, +# заполненного нулями +np.zeros(5) + +# или кортеж из чисел для указания количества +# нулей в каждом измерении +np.zeros((2, 3)) + +# + +# Массив из единиц +# Функция np.ones() + +# создадим трехмерный массив +np.ones((2, 2, 3)) + +# + +# Массив, заполненный заданным значением + +# создадим матрицу 2 х 3 и заполним ее цифрой четыре +np.full((2, 3), 4) + +# + +# Пустой массив Numpy +# функция np.empty() возвращает массив заданной размерности, +# но без инициализации его значений + +# создадим пустую матрицу 3 х 2 +np.empty((3, 2)) + +# + +# Функции np.zeros_like(), np.ones_like(), +# np.full_like(), np.empty_like() + +# создадим массив 2 x 3 с числами от 1 до 6 +a_var = np.arange(1, 7).reshape(2, 3) +a_var +# - + +# превратим его в массив с нулями +np.zeros_like(a_var) + +# единицами +np.ones_like(a_var) + +# двойками +np.full_like(a_var, 2) + +# и пустыми значениями +np.empty_like(a_var) + +# # Функция np.linspace() + +# + +# Функция np.linspace() позволяет указать диапазон начального +# и конечного значений, а также количество равноудаленных +# точек внутри этого диапазона (включая начальное и конечное значения). + +# создадим диапазон от 0 до 0,9 и +# разделим его на десять точек, включая 0 и 0,9 +np.linspace(0, 0.9, 10) +# - + +# с функцией np.arange мы точно знаем, где будут расположены точки +np.arange(0, 1, 0.1) + +# + +# Функцию np.linspace() удобно использовать +# для построения графиков функций. + + +# зададим размер графика в дюймах +plt.figure(figsize=(8, 6)) + +# зададим интервал, например, от -5 до 5 и +# сформируем на нем 5000 точек +# это будут наши координаты по оси x +x_var = np.linspace(-5, 5, 5000) + +# по оси y отложим квадрат этих точек +y_var = x_var**2 + +# создадим сетку +plt.grid() + +# выведем кривую и подписи на графике +plt.plot(x_var, y_var) +plt.xlabel("x", fontsize=14) +plt.ylabel("y", fontsize=14) + +# результатом будет парабола +plt.show() +# - + +# в качестве примера выведем первые десять точек, +# созданные функцией np.linspace() +x_var[:10] + +# # Функции np.random.rand() и np.random.randint() + +# + +# Массивы можно также создавать с помощью функций, +# генерирующих псевдослучайные числа. В частности, +# функция np.random.rand() создает массив заданной +# размерности, заполненный числами от 0 до 1 +# (единица в диапазон не входит). + +# создадим массив заданной размерности, заполненный числами +# в интервале [0, 1) +np.random.rand(4, 3) + +# + +# создадим массив целых чисел в заданном диапазоне +# (верхняя граница в него не входит) +# и с заданной размерностью + +np.random.randint(-3, 3, size=(2, 3, 2)) +# - + +# # Создание массива из функции + +# + +# Помимо вышеупомянутых способов массив можно +# создавать с помощью собственных функций. + + +# создадим собственную функцию, которая принимает два числа +# и возводит первое число в степень второго +def power(i_var: int | float, j_var: int | float) -> int | float: + """Возводит число в степень. + + Args: + i_var: Основание + j_var: Показатель степени + + Returns: + Результат i в степени j + """ + return pow(i_var, j_var) + + +# - + +# применим эту функцию к каждой ячейке массива 3 x 3 +np.fromfunction(power, (3, 3)) + +# также можно передать lambda-функцию, которая +# проверяет равенство двух чисел +np.fromfunction(lambda i_var, j_var: i_var == j_var, (3, 3)) + +# # Матрица csr и метод .toarray() + +# + +# Кроме этого в случае если данные хранятся в +# формате csr (сжатое хранения строкой, +# compressed sparse row), то мы можем +# преобразовать их обратно в массив Numpy с помощью метода .toarray(). + +# создадим матрицу с преобладанием нулевых значений +null_arr = np.array( + [[2, 0, 0, 1, 0, 0, 0], [0, 0, 3, 0, 0, 2, 0], [0, 0, 0, 1, 0, 0, 0]] +) +null_arr +# - + +# посчитаем долю нулевых значений +null_frac = 1.0 - np.count_nonzero(null_arr) / null_arr.size +print(null_frac) + +# + +# !{sys.executable} -m pip install scipy +# Преобразуем матрицу в формат csr. + + +# матрицу A удобно хранить в формате csr, +csr_arr = csr_matrix(null_arr) + +# где указывается положение ненулевого значения и само значение +print(csr_arr) +# - + +# восстановить массив Numpy можно с помощью метода .toarray() +rest_arr = csr_arr.toarray() +rest_arr + +# # Индексы и срезы + +# индексы +# рассмотрим двумерный массив +a_2d = np.array([[1, 2, 3], [4, 5, 6]]) +a_2d + +# первое значение атрибута shape относится к +# внешнему измерению +# второе - к внутреннему +a_2d.shape + +# выведем первый элемент внешнего измерения +a_2d[0] + +# второй индекс [0] позволяет обратиться +# к элементам внутреннего измерения +a_2d[0][0] + +# выведем значение шесть +a_2d[1][2] + +# Срез массива +# начнем с одномерного массива +b_slice = np.array([1, 2, 3, 4, 5, 6, 7, 8]) +b_slice + +# возьмем каждый второй элемент +# в интервале с 1-го по 6-й индекс +b_slice[1:6:2] + +# теперь возьмем двумерный массив +c_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]) +c_2d + +# сделаем срез из первой строки и первых двух столбцов +c_2d[0, :2] + +# а теперь возьмем обе строки во втором столбце +c_2d[:, 1] + +# выведем элемент в первой строке и первом столбце +c_2d[0, 0] + +# выведем элемент в последней строке и последнем столбце +c_2d[-1, -1] + +# возьмем всю вторую строку и каждый второй столбец +c_2d[1, ::2] + +# Массив 3D +# создадим трехмерный массив +d_3d = np.arange(16).reshape(4, 2, -1) +d_3d + +# выведем значение 10 +d_3d[2][1][0] + +# выведем третью и четвертую матрицу [2:] +# и в них вторую строку [1] и все столбцы [:] +d_3d[2:, 1, :] + +# выведем первые две матрицы массива +d_3d[:2] + +# выведем первые строки каждой матрицы +d_3d[:, 0, :] + +# + +# Оси массива +# Массив 2D + +# обратимся к двумерному массиву +arr_2d_axis = np.array([[1, 2], [3, 4]]) +arr_2d_axis +# - + +arr_2d_axis.shape + +# найдем сумму по столбцам (вдоль оси 0) +np.sum(arr_2d_axis, axis=0) + +# и сумму по строкам (вдоль оси 1) +np.sum(arr_2d_axis, axis=1) + +# если передать кортеж с указанием обеих осей (0, 1) +# сумма будет рассчитана вначале вдоль оси 0, затем вдоль оси 1 +np.sum(arr_2d_axis, axis=(0, 1)) + +# если ничего не указывать, сумма также будет +# рассчитана по всем элементам массива +np.sum(arr_2d_axis) + +# в этом случае под капотом стоит значение по +# умолчанию axis = None +np.sum(arr_2d_axis, axis=None) + +# axis = -1 соответствует последней оси массива, +# в данном случае, axis = 1 +np.sum(arr_2d_axis, axis=-1) + +# axis = -2 указывает на первую ось, то есть axis = 0 +np.sum(arr_2d_axis, axis=-2) + +# Массив 3D +# обратимся к трехмерному массиву +arr_3d_axis = np.arange(12).reshape(2, 2, 3) +arr_3d_axis + +# применим np.sum() с параметром axis = 0 +np.sum(arr_3d_axis, axis=0) + +# теперь возьмем первую матрицу +arr_3d_axis[0] + +# возьмем вторую матрицу +arr_3d_axis[1] + +# и поэлементно сложим их +arr_3d_axis[0] + arr_3d_axis[1] + +# Сложение вдоль второй оси (axis = 1) +# применим np.sum() +np.sum(arr_3d_axis, axis=1) + +# сложим столбцы первой +arr_3d_axis[0][0] + arr_3d_axis[0][1] + +# и второй матрицы +arr_3d_axis[1][0] + arr_3d_axis[1][1] + +# Сложение вдоль третьей оси (axis = 2) +# применим np.sum() +np.sum(arr_3d_axis, axis=2) + +# сложим элементы первой строки первой матрицы +arr_3d_axis[0][0][0] + arr_3d_axis[0][0][1] + arr_3d_axis[0][0][2] + +# второй строки первой матрицы +arr_3d_axis[0][1][0] + arr_3d_axis[0][1][1] + arr_3d_axis[0][1][2] + +# первой строки второй матрицы +arr_3d_axis[1][0][0] + arr_3d_axis[1][0][1] + arr_3d_axis[1][0][2] + +# второй строки второй матрицы +arr_3d_axis[1][1][0] + arr_3d_axis[1][1][1] + arr_3d_axis[1][1][2] + +# Сложение вдоль первой и второй осей (axis = (0, 1)) +# применим функцию np.sum() +np.sum(arr_3d_axis, axis=(0, 1)) + +# Сложение вдоль всех трех осей (axis = (0, 1, 2)) +np.sum(arr_3d_axis, axis=(0, 1, 2)) + +# # Операции с массивами + +# Вновь возьмем трехмерный массив. +# Функция len() +arr_3d_axis = np.arange(12).reshape(2, 2, 3) +arr_3d_axis + +len(arr_3d_axis) + +# чтобы вывести длину внутреннего измерения, +# т.е. вектора из трех элементов, +# нужно воспользоваться индексами +len(arr_3d_axis[0][0]) + +# Вхождение элемента в массив +# проверим, входит ли значение 3 в массив arr_3D +3 in arr_3d_axis + +# проверим, не входит ли значение 11 в массив arr_3D +11 not in arr_3d_axis + +# # Распакова массива + +# возьмем матрицу из трех строк и девяти столбцов +a_unpack = np.arange(1, 28).reshape(3, 9) +a_unpack + +# + +# каждую строку можно распаковать в отдельную переменную +x_unp, y_unp, z_unp = a_unpack + +# выведем первую переменную (строку) +x_unp + +# + +# теперь распакуем первый, последний и остальные элементы +# первой строки в отдельные переменные +x_sep, *y_sep, z_sep = a_unpack[0] + +# выведем каждую переменную +print(x_sep) +print(y_sep) +print(z_sep) +# - + +# # Изменение элементов массива + +# обратимся к двумерному массиву +arr_2d_index = np.array([[1, 2, 3], [4, 5, 6]]) +arr_2d_index + +# заменим первый элемент первой строки по его индексу +arr_2d_index[0, 0] = 2 +arr_2d_index + +# запишем значение 1 в первую строку +arr_2d_index[0] = 1 +arr_2d_index + +# запишем 0 в третий столбец массива +arr_2d_index[:, 2] = 0 +arr_2d_index + +# обратимся к трехмерному массиву +arr_3d_index = np.arange(12).reshape(2, 2, 3) +arr_3d_index + +# + +# выберем второй столбец второй матрицы и +# заменим значения столбца 7 и 10 на 0 и 1 +arr_3d_index[1, :, 1] = [0, 1] + +# при такой операции размер среза должен совпадать +# с количеством передаваемых значений +arr_3d_index +# - + +# заменим все элементы массива на число семь +arr_3d_index.fill(7) +arr_3d_index + +# # Сортировка массива и обратный порядок его элементов + +# Функция np.sort() +# возьмем двумерный массив +a_sort = np.array([[4, 8, 2], [2, 3, 1]]) +a_sort + +# по умолчанию сортировка идет с параметром axis = -1 (последняя ось) +np.sort(a_sort) + +# для двумерного массива это ось 1 +np.sort(a_sort, axis=1) + +# посмотрим на сортировку по оси 0 +np.sort(a_sort, axis=0) + +# axis = None вначале возвращает одномерный массив, +# а затем сортирует +np.sort(a_sort, axis=None) + +# # Обратный порядок элементов массива через оператор среза + +# оператор среза с параметром шага -1 +# задает обратный порядок элементов массива +reverse_arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])[::-1] +print(reverse_arr) + +# обратный порядок можно совмещать со срезами +reverse_arr_slice = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])[-3:3:-1] +print(reverse_arr_slice) + +# в двумерном массиве +a_rev = np.array([[4, 8, 2], [2, 3, 1], [1, 7, 2]]) +a_rev + +# можно задать обратный порядок по двум измерениям (axis = (0, 1)) +a_rev[::-1, ::-1] + +# обратный порядок по внешнему (axis = 0) +a_rev[::-1] + +# и внутреннему измерению (axis = 1) +a_rev[:, ::-1] + +# + +# Обратный порядок через функцию np.flip() + +# по умолчанию, задает обратный порядок по двум измерениям +# то же самое, что axis = (0, 1) +np.flip(a_rev) +# - + +# можно также задать обратный порядок по внешнему +np.flip(a_rev, axis=0) + +# и внутреннему измерениям +np.flip(a_rev, axis=1) + +# для сортировки массива в убывающем порядке +a_rev = np.array([4, 2, 6, 1, 7, 3, 5]) + +# мы можем последовательно применить np.sort() и оператор среза +sorted_desc = np.sort(a_rev)[::-1] +print(sorted_desc) + +# исходный массив не изменился +a_rev + +# можно и обратном порядке, но уже с методом .sort() +a_rev[::-1].sort() + +# изменение стало постоянным +a_rev + +# Изменение размерности +# возьмем простой трехмерный массив +arr_3d_index = np.arange(12).reshape(2, 2, 3) +arr_3d_index + +# в нем 12 элементов +arr_3d_index.size + +# поменяем размерность при сохранении общего количества элементов +arr_2d_index = arr_3d_index.reshape(2, 6) +arr_2d_index + +# функция np.resize() позволяет не сохранять +# прежнее количество элементов +# существующие элементы копируются в новые ячейки +np.resize(arr_2d_index, (3, 6)) + +# arr_2D ссылается на другой массив, поэтому +# сначала нужно сделать его копию +arr_2d_copy = arr_2d_index.copy() + +# метод .resize() сделает копию, изменит размерность +# и заполнит пропуски нулями +arr_2d_copy.resize(4, 6) +arr_2d_copy + +# .flatten() переводит ("вытягивает") массив в одно измерение и +# создает копию исходного массива (как метод .copy()) +arr_3d_index.flatten() + +# .ravel() делает то же самое, +# но не создает копию исходного массива и поэтому быстрее чем .flatten() +arr_3d_index.ravel() + +# np.newaxis добавляет измерение в массиве +a_newaxis = np.array([1, 2, 3]) +a_newaxis.shape + +# + +# добавим первое измерение +b_newaxis = a_newaxis[np.newaxis, :] + +print(b_newaxis) +print(b_newaxis.shape) + +# + +# добавим второе измерение +c_two_newaxis = a_newaxis[:, np.newaxis] + +print(c_two_newaxis) +print(c_two_newaxis.shape) +# - + +# Функция np.expand_dims() +# возьмем двумерный массив +a_expand = np.array([[1, 2], [3, 4]]) +a_expand + +# добавим внешнее измерение +np.expand_dims(a_expand, axis=0) + +# добавим измерение "по середине" +np.expand_dims(a_expand, axis=1) + +# возьмем массив 4D +# первое и последнее измерения содержат по одному элементу +arr_4d_resize = np.arange(9).reshape(1, 3, 3, 1) +arr_4d_resize + +# удалим эти измерения с помощью функции np.squeeze() +np.squeeze(arr_4d_resize) + +# + +# новый массив имеет только два измерения +arr_squeeze = np.squeeze(arr_4d_resize).shape + +print(arr_squeeze) +# - + +# # Объединение массивов + +# создадим два квадратных массива +a_sq = np.arange(4).reshape(2, 2) +a_sq + +b_sq = np.arange(4, 8).reshape(2, 2) +b_sq + +# объединим два массива вдоль оси 0 без +# (!) добавления нового измерения +np.concatenate((a_sq, b_sq), axis=0) + +# то же самое вдоль оси 1 +np.concatenate((a_sq, b_sq), axis=1) + +# Функция np.stack() +# здесь отличие в том, что мы добавляем новую ось (измерение) +np.stack((a_sq, b_sq), axis=0) + +# при axis = 1 мы объединяем первые и вторые строки двух массивов +np.stack((a_sq, b_sq), axis=1) + +# при axis = 2 объединяются элементы с одинаковыми индексами +np.stack((a_sq, b_sq), axis=2) diff --git a/python/makarov/chapter_11_iterator.ipynb b/python/makarov/chapter_11_iterator.ipynb new file mode 100644 index 00000000..1aad205c --- /dev/null +++ b/python/makarov/chapter_11_iterator.ipynb @@ -0,0 +1,1175 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "71360fb5", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Итераторы и генераторы.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "0460c890", + "metadata": {}, + "source": [ + "# Итерируемый объект и итератор" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d53c37a2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n", + "3\n" + ] + } + ], + "source": [ + "# Основные определения\n", + "# Итератор — это объект, который позволяет перебирать элементы коллекции\n", + "from collections.abc import Iterator\n", + "from itertools import chain, count, cycle\n", + "\n", + "for num in [1, 2, 3]:\n", + " print(num)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d4d33140", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# встроенная функция iter() вызывает метод .__iter__(),\n", + "# создающий итератор\n", + "iter([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4910a7d5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "1\n", + "2\n", + "3\n" + ] + } + ], + "source": [ + "# Итератор (iterator) позволяет извлекать элементы итерируемого объекта\n", + "# за одним отслеживая, какой именно элемент был извлечен на данной\n", + "# итерации класс итератора, таким образом, должен включать два метода:\n", + "# метод .__iter__(), который возвращает сам этот объект; и\n", + "# метод .__next__(), возвращающий следующий элемент последовательности.\n", + "\n", + "iterable_object = [1, 2, 3]\n", + "\n", + "iterator = iter(iterable_object)\n", + "print(iterator)\n", + "print()\n", + "\n", + "print(next(iterator))\n", + "print(next(iterator))\n", + "print(next(iterator))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b671ab60", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n", + "3\n" + ] + } + ], + "source": [ + "# При использовании цикла итератор создается автоматически.\n", + "\n", + "for item in iterable_object:\n", + " print(item)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9be44ac7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A: 1\n", + "A: 2\n", + "A: 3\n", + "B: 1\n" + ] + } + ], + "source": [ + "# разделение итерируемого объекта и итератора позволяет оставлять\n", + "# нетронутыми данные в итерируемом объекте и обращаться к ним столько\n", + "# раз, сколько это необходимо с помощью итератора.\n", + "\n", + "iterable_object = [1, 2, 3]\n", + "\n", + "iterator_a = iter(iterable_object)\n", + "iterator_b = iter(iterable_object)\n", + "\n", + "print(f\"A: {next(iterator_a)}\")\n", + "print(f\"A: {next(iterator_a)}\")\n", + "print(f\"A: {next(iterator_a)}\")\n", + "print(f\"B: {next(iterator_b)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "90700131", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 3]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "iterable_object" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0461ed60", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "([], [2, 3])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Функция list() позволяет обойти и вернуть все (оставшиеся) элементы\n", + "# конкретного итератора.\n", + "\n", + "result_a = list(iterator_a)\n", + "result_b = list(iterator_b)\n", + "result_a, result_b" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "72e11bc4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n", + "3\n" + ] + } + ], + "source": [ + "# Используем list для итерации вместо однобуквенной переменной\n", + "for item in [1, 2, 3]:\n", + " print(item)" + ] + }, + { + "cell_type": "markdown", + "id": "42116a24", + "metadata": {}, + "source": [ + "# Отсутствие «обратного хода»" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "39db388a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n", + "3\n" + ] + } + ], + "source": [ + "# Логично, что итератор не позволяет двигаться в обратном направлении\n", + "# от последующего элемента к предыдущему.\n", + "\n", + "iterator_c = iter(iterable_object)\n", + "\n", + "for first_item in iterator_c:\n", + " print(first_item)\n", + " break\n", + "\n", + "for next_item in iterator_c:\n", + " print(next_item)" + ] + }, + { + "cell_type": "markdown", + "id": "b4a2aa28", + "metadata": {}, + "source": [ + "# Функция zip()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "4d2673ad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1, 1)\n", + "(2, 2)\n", + "(3, 3)\n" + ] + } + ], + "source": [ + "# Помимо функции iter() еще одной функцией, возвращающей итератор\n", + "# из итерируемого объекта, является функция zip().\n", + "\n", + "iterator_tuple = zip(iterable_object, iterable_object)\n", + "\n", + "print(next(iterator_tuple))\n", + "print(next(iterator_tuple))\n", + "print(next(iterator_tuple))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "08f72e9e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1, 1)\n", + "(2, 2)\n", + "(3, 3)\n" + ] + } + ], + "source": [ + "# На практике конечно удобнее передать итерируемые объекты в функцию\n", + "# zip() и создавать и вызывать метод .__next__() итератора в цикле.\n", + "\n", + "for item_pair in zip(iterable_object, iterable_object):\n", + " print(item_pair)" + ] + }, + { + "cell_type": "markdown", + "id": "11c21a57", + "metadata": {}, + "source": [ + "# Примеры итераторов" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "85965158", + "metadata": {}, + "outputs": [], + "source": [ + "# первый итератор принимает на вход некоторую последовательность чисел\n", + "# и возвращает квадрат каждого из них.\n", + "\n", + "\n", + "class Square:\n", + " \"\"\"Iterator that returns the square of each element in a sequence.\"\"\"\n", + "\n", + " def __init__(self, sequence: list[int]) -> None:\n", + " \"\"\"Initialize Square iterator with a sequence.\n", + "\n", + " Args:\n", + " sequence: List of integers to square.\n", + " \"\"\"\n", + " self._seq = sequence\n", + " self._idx = 0\n", + "\n", + " def __iter__(self) -> \"Square\":\n", + " \"\"\"Return the iterator object itself.\"\"\"\n", + " return self\n", + "\n", + " def __next__(self) -> int:\n", + " \"\"\"Return the next squared element.\"\"\"\n", + " if self._idx < len(self._seq):\n", + " squared_val = self._seq[self._idx] ** 2\n", + " self._idx += 1\n", + " return squared_val\n", + " raise StopIteration" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ed568e88", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.Square at 0x238d85c7620>" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "square_iter = Square([1, 2, 3, 4, 5])\n", + "square_iter" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "dd394bcd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "4\n", + "9\n", + "16\n", + "25\n" + ] + } + ], + "source": [ + "for squared_elem in square_iter:\n", + " print(squared_elem)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "5b5cc50b", + "metadata": {}, + "outputs": [], + "source": [ + "# Итераторы также могут самостоятельно создавать новые данные.\n", + "\n", + "\n", + "class Counter:\n", + " \"\"\"Iterator that generates numbers from start to stop.\"\"\"\n", + "\n", + " def __init__(self, start: int = 3, stop: int = 9) -> None:\n", + " \"\"\"Initialize Counter iterator.\n", + "\n", + " Args:\n", + " start: Starting value (default 3).\n", + " stop: Stopping value (default 9).\n", + " \"\"\"\n", + " self._current = start - 1\n", + " self._stop = stop\n", + "\n", + " def __iter__(self) -> \"Counter\":\n", + " \"\"\"Return the iterator object itself.\"\"\"\n", + " return self\n", + "\n", + " def __next__(self) -> int:\n", + " \"\"\"Return the next number in the sequence.\"\"\"\n", + " self._current += 1\n", + " if self._current < self._stop:\n", + " return self._current\n", + " raise StopIteration" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "96d7e070", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.Counter at 0x238d85c7770>" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "counter = Counter()\n", + "counter" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "31cbe0c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n", + "4\n" + ] + } + ], + "source": [ + "print(next(counter))\n", + "print(next(counter))" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "c9e0b3a5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5\n", + "6\n", + "7\n", + "8\n" + ] + } + ], + "source": [ + "for counter_val in counter:\n", + " print(counter_val)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "53a3fe1c", + "metadata": {}, + "outputs": [], + "source": [ + "# Класс Iterator модуля collections.abc\n", + "\n", + "\n", + "class Counter2(Iterator[int]):\n", + " \"\"\"Counter iterator inheriting from collections.abc.Iterator.\"\"\"\n", + "\n", + " def __init__(self, start: int = 3, stop: int = 9) -> None:\n", + " \"\"\"Initialize Counter2 iterator.\n", + "\n", + " Args:\n", + " start: Starting value (default 3).\n", + " stop: Stopping value (default 9).\n", + " \"\"\"\n", + " self._current = start - 1\n", + " self._stop = stop\n", + "\n", + " def __next__(self) -> int:\n", + " \"\"\"Return the next number in the sequence.\"\"\"\n", + " self._current += 1\n", + " if self._current < self._stop:\n", + " return self._current\n", + " raise StopIteration" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "336e8d5e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n", + "4\n", + "5\n", + "6\n", + "7\n", + "8\n" + ] + } + ], + "source": [ + "for counter2_val in Counter2():\n", + " print(counter2_val)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "61a4bf54", + "metadata": {}, + "outputs": [], + "source": [ + "# Бесконечный итератор\n", + "\n", + "\n", + "class FibIterator:\n", + " \"\"\"Iterator that generates Fibonacci sequence infinitely.\"\"\"\n", + "\n", + " def __init__(self) -> None:\n", + " \"\"\"Initialize Fibonacci iterator.\"\"\"\n", + " self._idx = 0\n", + " self._current = 0\n", + " self._next = 1\n", + "\n", + " def __iter__(self) -> \"FibIterator\":\n", + " \"\"\"Return the iterator object itself.\"\"\"\n", + " return self\n", + "\n", + " def __next__(self) -> int:\n", + " \"\"\"Return the next Fibonacci number.\"\"\"\n", + " self._idx += 1\n", + " self._current, self._next = (self._next, self._current + self._next)\n", + " return self._current" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "b67acf52", + "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": [ + "limit = 10\n", + "\n", + "for fib_num in FibIterator():\n", + " print(fib_num)\n", + " limit -= 1\n", + " if limit == 0:\n", + " break" + ] + }, + { + "cell_type": "markdown", + "id": "986d3380", + "metadata": {}, + "source": [ + "# Генераторы" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "0484810c", + "metadata": {}, + "outputs": [], + "source": [ + "# Простой пример\n", + "# Рассмотрим функцию, которая создает последовательность чисел.\n", + "\n", + "\n", + "def make_sequence(seq_length: int) -> list[int]:\n", + " \"\"\"Generate a list of integers from 1 to seq_length.\n", + "\n", + " Args:\n", + " seq_length: The length of the sequence.\n", + "\n", + " Returns:\n", + " A list of integers from 1 to seq_length.\n", + " \"\"\"\n", + " res = [elem for elem in range(1, seq_length + 1)]\n", + " return res" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "0226ee72", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 3, 4, 5]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "make_sequence(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "26fdaadd", + "metadata": {}, + "outputs": [], + "source": [ + "# Если использовать ключевое слово yield вместо return, то функция\n", + "# вернет не последовательность, а объект-генератор.\n", + "\n", + "\n", + "def sequence_gen(seq_length: int) -> Iterator[int]:\n", + " \"\"\"Generate integers from 1 to seq_length using yield.\n", + "\n", + " Args:\n", + " seq_length: The length of the sequence.\n", + "\n", + " Yields:\n", + " Integers from 1 to seq_length.\n", + " \"\"\"\n", + " yield from range(1, seq_length + 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b9649de0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sequence_gen(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "f53a8879", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n" + ] + } + ], + "source": [ + "seq_5 = sequence_gen(5)\n", + "\n", + "print(next(seq_5))\n", + "print(next(seq_5))" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "53171cee", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n", + "4\n", + "5\n" + ] + } + ], + "source": [ + "# Все элементы последовательности можно вывести с помощью цикла.\n", + "\n", + "\n", + "for seq_elem in seq_5:\n", + " print(seq_elem)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "7e2069dd", + "metadata": {}, + "outputs": [], + "source": [ + "# Если вызвать метод .__next__() после того, как генератор выдаст все\n", + "# имеющиеся значения, сработает исключение StopIteration.\n", + "\n", + "# next(seq_5)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "ccb1ff0d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + " at 0x...>" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Generator comprehension\n", + "# Создать объект-генератор можно в одну строчку аналогично\n", + "# list comprehension, но с использованием круглых скобок.\n", + "\n", + "gen_obj = (elem for elem in range(1, 5 + 1))\n", + "gen_obj" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "4ffc5cab", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 3, 4, 5]" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# После этого объект можно превратить в список\n", + "\n", + "list(elem for elem in range(1, 5 + 1))" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "bf0b05dc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "15" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# или, например, найти сумму элементов\n", + "\n", + "sum(elem for elem in range(1, 5 + 1))" + ] + }, + { + "cell_type": "markdown", + "id": "febeafb2", + "metadata": {}, + "source": [ + "# Модуль itertools" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "f7c9389b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "1.5\n", + "2.0\n" + ] + } + ], + "source": [ + "# Функция count()\n", + "\n", + "natural_numbers = count(start=1, step=0.5)\n", + "\n", + "for num in natural_numbers:\n", + " print(num)\n", + " if num == 2.0:\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "f5b23612", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(0, 'A')\n", + "(1, 'B')\n", + "(2, 'C')\n", + "(3, 'D')\n" + ] + } + ], + "source": [ + "# На практике функцию count() можно использовать, например,\n", + "# для индексирования элементов списка.\n", + "\n", + "str_list = [\"A\", \"B\", \"C\", \"D\"]\n", + "for index_val in zip(count(), str_list):\n", + " print(index_val)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d5bc789", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-2" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Кроме этого, совместив count() с map() можно получить\n", + "# значения некоторой функции.\n", + "\n", + "\n", + "def func(val_quad: int) -> int:\n", + " \"\"\"Calculate quadratic function x^2 + x - 2.\n", + "\n", + " Args:\n", + " val: Input value.\n", + "\n", + " Returns:\n", + " Result of x^2 + x - 2.\n", + " \"\"\"\n", + " return val_quad**2 + val_quad - 2\n", + "\n", + "\n", + "f_x = map(func, count())\n", + "next(f_x)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "438b385e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "4\n", + "10\n", + "18\n" + ] + } + ], + "source": [ + "for func_val in f_x:\n", + " # выполнение продолжится со второго значения итератора\n", + " print(func_val)\n", + " if func_val > 10:\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "db1639fb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n", + "3\n", + "1\n", + "2\n" + ] + } + ], + "source": [ + "# Функция cycle()\n", + "\n", + "int_list = [1, 2, 3]\n", + "cycle_iter = cycle(int_list)\n", + "\n", + "limit = 5\n", + "for cycle_item in cycle_iter:\n", + " print(cycle_item)\n", + " limit -= 1\n", + " if limit == 0:\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "3cb6ad83", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "P\n", + "y\n", + "t\n", + "h\n", + "o\n", + "n\n", + "P\n", + "y\n", + "t\n", + "h\n" + ] + } + ], + "source": [ + "# Строки, как мы помним, также являются итерируемым объектом.\n", + "\n", + "string = \"Python\"\n", + "string_iter = cycle(string)\n", + "\n", + "limit = 10\n", + "for cycle_char in string_iter:\n", + " print(cycle_char)\n", + " limit -= 1\n", + " if limit == 0:\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "fd79df35", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Функция chain()\n", + "\n", + "# Функция chain() объединяет (сцепляет) несколько итерируемых\n", + "# объектов в один итератор.\n", + "\n", + "chain_result = chain([\"abc\", \"d\", \"e\", \"f\"], \"abc\", [1, 2, 3])\n", + "chain_result" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "cbbd097e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['abc', 'd', 'e', 'f', 'a', 'b', 'c', 1, 2, 3]" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(chain_result)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "20577e81", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['a', 'b', 'c', 'd', 'e', 'f']" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(chain.from_iterable([\"abc\", \"def\"]))" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "996fda0b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "45" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(chain.from_iterable([[1, 2, 3], [4, 5, 6], [7, 8, 9]]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b6d07ad", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_11_iterator.py b/python/makarov/chapter_11_iterator.py new file mode 100644 index 00000000..6e25e656 --- /dev/null +++ b/python/makarov/chapter_11_iterator.py @@ -0,0 +1,423 @@ +"""Итераторы и генераторы.""" + +# # Итерируемый объект и итератор + +# + +# Основные определения +# Итератор — это объект, который позволяет перебирать элементы коллекции +from collections.abc import Iterator +from itertools import chain, count, cycle + +for num in [1, 2, 3]: + print(num) +# - + +# встроенная функция iter() вызывает метод .__iter__(), +# создающий итератор +iter([1, 2, 3]) + +# + +# Итератор (iterator) позволяет извлекать элементы итерируемого объекта +# за одним отслеживая, какой именно элемент был извлечен на данной +# итерации класс итератора, таким образом, должен включать два метода: +# метод .__iter__(), который возвращает сам этот объект; и +# метод .__next__(), возвращающий следующий элемент последовательности. + +iterable_object = [1, 2, 3] + +iterator = iter(iterable_object) +print(iterator) +print() + +print(next(iterator)) +print(next(iterator)) +print(next(iterator)) + +# + +# При использовании цикла итератор создается автоматически. + +for item in iterable_object: + print(item) + +# + +# разделение итерируемого объекта и итератора позволяет оставлять +# нетронутыми данные в итерируемом объекте и обращаться к ним столько +# раз, сколько это необходимо с помощью итератора. + +iterable_object = [1, 2, 3] + +iterator_a = iter(iterable_object) +iterator_b = iter(iterable_object) + +print(f"A: {next(iterator_a)}") +print(f"A: {next(iterator_a)}") +print(f"A: {next(iterator_a)}") +print(f"B: {next(iterator_b)}") +# - + +iterable_object + +# + +# Функция list() позволяет обойти и вернуть все (оставшиеся) элементы +# конкретного итератора. + +result_a = list(iterator_a) +result_b = list(iterator_b) +result_a, result_b +# - + +# Используем list для итерации вместо однобуквенной переменной +for item in [1, 2, 3]: + print(item) + +# # Отсутствие «обратного хода» + +# + +# Логично, что итератор не позволяет двигаться в обратном направлении +# от последующего элемента к предыдущему. + +iterator_c = iter(iterable_object) + +for first_item in iterator_c: + print(first_item) + break + +for next_item in iterator_c: + print(next_item) +# - + +# # Функция zip() + +# + +# Помимо функции iter() еще одной функцией, возвращающей итератор +# из итерируемого объекта, является функция zip(). + +iterator_tuple = zip(iterable_object, iterable_object) + +print(next(iterator_tuple)) +print(next(iterator_tuple)) +print(next(iterator_tuple)) + +# + +# На практике конечно удобнее передать итерируемые объекты в функцию +# zip() и создавать и вызывать метод .__next__() итератора в цикле. + +for item_pair in zip(iterable_object, iterable_object): + print(item_pair) +# - + +# # Примеры итераторов + +# + +# первый итератор принимает на вход некоторую последовательность чисел +# и возвращает квадрат каждого из них. + + +class Square: + """Iterator that returns the square of each element in a sequence.""" + + def __init__(self, sequence: list[int]) -> None: + """Initialize Square iterator with a sequence. + + Args: + sequence: List of integers to square. + """ + self._seq = sequence + self._idx = 0 + + def __iter__(self) -> "Square": + """Return the iterator object itself.""" + return self + + def __next__(self) -> int: + """Return the next squared element.""" + if self._idx < len(self._seq): + squared_val = self._seq[self._idx] ** 2 + self._idx += 1 + return squared_val + raise StopIteration + + +# - + +square_iter = Square([1, 2, 3, 4, 5]) +square_iter + +for squared_elem in square_iter: + print(squared_elem) + +# + +# Итераторы также могут самостоятельно создавать новые данные. + + +class Counter: + """Iterator that generates numbers from start to stop.""" + + def __init__(self, start: int = 3, stop: int = 9) -> None: + """Initialize Counter iterator. + + Args: + start: Starting value (default 3). + stop: Stopping value (default 9). + """ + self._current = start - 1 + self._stop = stop + + def __iter__(self) -> "Counter": + """Return the iterator object itself.""" + return self + + def __next__(self) -> int: + """Return the next number in the sequence.""" + self._current += 1 + if self._current < self._stop: + return self._current + raise StopIteration + + +# - + +counter = Counter() +counter + +print(next(counter)) +print(next(counter)) + +for counter_val in counter: + print(counter_val) + +# + +# Класс Iterator модуля collections.abc + + +class Counter2(Iterator[int]): + """Counter iterator inheriting from collections.abc.Iterator.""" + + def __init__(self, start: int = 3, stop: int = 9) -> None: + """Initialize Counter2 iterator. + + Args: + start: Starting value (default 3). + stop: Stopping value (default 9). + """ + self._current = start - 1 + self._stop = stop + + def __next__(self) -> int: + """Return the next number in the sequence.""" + self._current += 1 + if self._current < self._stop: + return self._current + raise StopIteration + + +# - + +for counter2_val in Counter2(): + print(counter2_val) + +# + +# Бесконечный итератор + + +class FibIterator: + """Iterator that generates Fibonacci sequence infinitely.""" + + def __init__(self) -> None: + """Initialize Fibonacci iterator.""" + self._idx = 0 + self._current = 0 + self._next = 1 + + def __iter__(self) -> "FibIterator": + """Return the iterator object itself.""" + return self + + def __next__(self) -> int: + """Return the next Fibonacci number.""" + self._idx += 1 + self._current, self._next = (self._next, self._current + self._next) + return self._current + + +# + +limit = 10 + +for fib_num in FibIterator(): + print(fib_num) + limit -= 1 + if limit == 0: + break +# - + +# # Генераторы + +# + +# Простой пример +# Рассмотрим функцию, которая создает последовательность чисел. + + +def make_sequence(seq_length: int) -> list[int]: + """Generate a list of integers from 1 to seq_length. + + Args: + seq_length: The length of the sequence. + + Returns: + A list of integers from 1 to seq_length. + """ + res = [elem for elem in range(1, seq_length + 1)] + return res + + +# - + +make_sequence(5) + +# + +# Если использовать ключевое слово yield вместо return, то функция +# вернет не последовательность, а объект-генератор. + + +def sequence_gen(seq_length: int) -> Iterator[int]: + """Generate integers from 1 to seq_length using yield. + + Args: + seq_length: The length of the sequence. + + Yields: + Integers from 1 to seq_length. + """ + yield from range(1, seq_length + 1) + + +# - + +sequence_gen(5) + +# + +seq_5 = sequence_gen(5) + +print(next(seq_5)) +print(next(seq_5)) + +# + +# Все элементы последовательности можно вывести с помощью цикла. + + +for seq_elem in seq_5: + print(seq_elem) + +# + +# Если вызвать метод .__next__() после того, как генератор выдаст все +# имеющиеся значения, сработает исключение StopIteration. + +# next(seq_5) + +# + +# Generator comprehension +# Создать объект-генератор можно в одну строчку аналогично +# list comprehension, но с использованием круглых скобок. + +gen_obj = (elem for elem in range(1, 5 + 1)) +gen_obj + +# + +# После этого объект можно превратить в список + +list(elem for elem in range(1, 5 + 1)) + +# + +# или, например, найти сумму элементов + +sum(elem for elem in range(1, 5 + 1)) +# - + +# # Модуль itertools + +# + +# Функция count() + +natural_numbers = count(start=1, step=0.5) + +for num in natural_numbers: + print(num) + if num == 2.0: + break + +# + +# На практике функцию count() можно использовать, например, +# для индексирования элементов списка. + +str_list = ["A", "B", "C", "D"] +for index_val in zip(count(), str_list): + print(index_val) + +# + +# Кроме этого, совместив count() с map() можно получить +# значения некоторой функции. + + +def func(val_quad: int) -> int: + """Calculate quadratic function x^2 + x - 2. + + Args: + val: Input value. + + Returns: + Result of x^2 + x - 2. + """ + return val_quad**2 + val_quad - 2 + + +f_x = map(func, count()) +next(f_x) +# - + +for func_val in f_x: + # выполнение продолжится со второго значения итератора + print(func_val) + if func_val > 10: + break + +# + +# Функция cycle() + +int_list = [1, 2, 3] +cycle_iter = cycle(int_list) + +limit = 5 +for cycle_item in cycle_iter: + print(cycle_item) + limit -= 1 + if limit == 0: + break + +# + +# Строки, как мы помним, также являются итерируемым объектом. + +string = "Python" +string_iter = cycle(string) + +limit = 10 +for cycle_char in string_iter: + print(cycle_char) + limit -= 1 + if limit == 0: + break + +# + +# Функция chain() + +# Функция chain() объединяет (сцепляет) несколько итерируемых +# объектов в один итератор. + +chain_result = chain(["abc", "d", "e", "f"], "abc", [1, 2, 3]) +chain_result +# - + +list(chain_result) + +list(chain.from_iterable(["abc", "def"])) + +sum(chain.from_iterable([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) diff --git a/python/makarov/chapter_12_decorators.ipynb b/python/makarov/chapter_12_decorators.ipynb new file mode 100644 index 00000000..092f30f5 --- /dev/null +++ b/python/makarov/chapter_12_decorators.ipynb @@ -0,0 +1,1625 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "46efec3f", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Decorators.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f8a0793", + "metadata": {}, + "outputs": [], + "source": [ + "# Декораторы\n", + "# Декораторы - это функции, которые принимают другую функцию в качестве аргумента\n", + "# расширяют её функциональность без изменения её кода и возвращают новую функцию." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91c0f8ba", + "metadata": {}, + "outputs": [], + "source": [ + "# Объекты первого класса\n", + "#\n", + "# Функции в Питоне представляют собой объекты первого класса\n", + "# (first class objects), что означает, что их можно присваивать\n", + "# переменной, возвращать из функции или передавать другой\n", + "# функции в качестве аргумента." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "923b8633", + "metadata": {}, + "outputs": [], + "source": [ + "import functools\n", + "import time\n", + "\n", + "# Можно также воспользоваться функцией functools.update_wrapper().\n", + "from functools import update_wrapper\n", + "\n", + "# Присвоение функции переменной\n", + "from typing import Callable, ParamSpec, TypeVar\n", + "\n", + "\n", + "def say_hello(name: str) -> None:\n", + " \"\"\"Print greeting message.\"\"\"\n", + " print(f\"Привет, {name}!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1c918bfd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Привет, Алексей!\n" + ] + } + ], + "source": [ + "# присвоим эту функцию переменной (без скобок)\n", + "say_hello_function = say_hello\n", + "# вызовем функцию из новой переменной\n", + "say_hello_function(\"Алексей\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7fff426", + "metadata": {}, + "outputs": [], + "source": [ + "# Передача функции в качестве аргумента\n", + "\n", + "\n", + "Rval = TypeVar(\"Rval\")\n", + "\n", + "\n", + "def calculate_result(\n", + " operation: Callable[[int, int], Rval], first: int, second: int\n", + ") -> Rval:\n", + " \"\"\"Calculate using provided operation.\"\"\"\n", + " return operation(first, second)\n", + "\n", + "\n", + "def add(first: int, second: int) -> int:\n", + " \"\"\"Add two numbers.\"\"\"\n", + " return first + second\n", + "\n", + "\n", + "def subtract(first: int, second: int) -> int:\n", + " \"\"\"Subtract two numbers.\"\"\"\n", + " return first - second\n", + "\n", + "\n", + "def multiply(first: int, second: int) -> int:\n", + " \"\"\"Multiply two numbers.\"\"\"\n", + " return first * second\n", + "\n", + "\n", + "def divide(first: int, second: int) -> float:\n", + " \"\"\"Divide two numbers.\"\"\"\n", + " return first / second" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3f2ab319", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.3333333333333333\n" + ] + } + ], + "source": [ + "# Разделим единицу на три.\n", + "\n", + "\n", + "result = calculate_result(divide, 1, 3)\n", + "print(result) # 0.3333333333333333" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d39f3b6", + "metadata": {}, + "outputs": [], + "source": [ + "# Внутрение функции\n", + "# Внутренние (inner) или вложенные (nested) функции\n", + "# представляют собой функции, объявленные и вызванные внутри других функций.\n", + "# Вызов внутренней функции\n", + "\n", + "\n", + "def outer() -> None:\n", + " \"\"\"Call outer and inner functions.\"\"\"\n", + " print(\"Вызов внешней функции.\")\n", + "\n", + " # обратите внимание, мы объявляем, а затем\n", + " def inner() -> None:\n", + " \"\"\"Call inner function.\"\"\"\n", + " print(\"Вызов внутренней функции.\")\n", + "\n", + " # вызываем внутреннюю функцию\n", + " inner()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "38efb32a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Вызов внешней функции.\n", + "Вызов внутренней функции.\n" + ] + } + ], + "source": [ + "outer()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "45a9ad54", + "metadata": {}, + "outputs": [], + "source": [ + "# Вызвать внутреннюю функцию не получится.\n", + "\n", + "# inner() # Вызвать внутреннюю функцию не получится." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6813496", + "metadata": {}, + "outputs": [], + "source": [ + "# Возвращение функции из функции\n", + "# Функция может возвращать другую функцию. В примере ниже функция\n", + "# create_multiplier() создает множитель (factor) для передаваемого во\n", + "# внуреннюю функцию multiplier() числа (number).\n", + "\n", + "\n", + "def create_multiplier(factor: int) -> Callable[[int], int]:\n", + " \"\"\"Create multiplier function.\"\"\"\n", + "\n", + " def multiplier(number: int) -> int:\n", + " \"\"\"Multiply number by factor.\"\"\"\n", + " return number * factor\n", + "\n", + " return multiplier" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "99b88fc3", + "metadata": {}, + "outputs": [], + "source": [ + "# Создадим две ссылки на внутреннюю функцию multiplier(),\n", + "# передав ей в качестве параметра множитель,\n", + "# равный соответственно двум и трем.\n", + "\n", + "\n", + "double = create_multiplier(factor=2)\n", + "triple = create_multiplier(factor=3)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "740f068c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(4, 6)" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Умножим число два на каждый из множителей.\n", + "\n", + "result_double: int = double(2)\n", + "result_triple: int = triple(2)\n", + "print(result_double, result_triple)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "150a3a2f", + "metadata": {}, + "outputs": [], + "source": [ + "# Заметим, что код в примере выше можно сократить с помощью\n", + "# lambda-функции.\n", + "\n", + "\n", + "def create_multiplier_lambda(factor: int) -> Callable[[int], int]:\n", + " \"\"\"Create multiplier function using lambda.\"\"\"\n", + " return lambda number: factor * number" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "c0b7359a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "triple = create_multiplier_lambda(factor=3)\n", + "print(triple(2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62fc404f", + "metadata": {}, + "outputs": [], + "source": [ + "# Замыкание\n", + "#\n", + "# Замыкание (closure) — это внутренняя функция, которая сохраняет доступ\n", + "# к переменным из внешней (окружающей) функции даже после завершения\n", + "# выполнения внешней функции." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f0cca4a", + "metadata": {}, + "outputs": [], + "source": [ + "# Знакомство с декораторами\n", + "# Простой декоратор\n", + "\n", + "\n", + "T = TypeVar(\"T\", bound=Callable[[], None])\n", + "\n", + "\n", + "def simple_decorator(func: T) -> T:\n", + " \"\"\"Wrap function with simple decorator.\"\"\"\n", + "\n", + " def wrapper() -> None:\n", + " print(f\"{func.__name__} called.\")\n", + " func()\n", + " print(\"simple_decorator finished.\")\n", + "\n", + " return wrapper # type: ignore[return-value]\n", + "\n", + "\n", + "def say_hello_simple() -> None:\n", + " \"\"\"Greet user.\"\"\"\n", + " print(\"Привет!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "5019ff64", + "metadata": {}, + "outputs": [], + "source": [ + "say_hello_simple = simple_decorator(say_hello_simple)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "f6b2e35e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Текст до вызова функции func().\n", + "Привет!\n", + "Текст после вызова функции func().\n" + ] + } + ], + "source": [ + "# Вызовем функцию say_hello().\n", + "say_hello_simple()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f61d9731", + "metadata": {}, + "outputs": [], + "source": [ + "# Таким образом, декоратор — это функция, которая принимает в\n", + "# качестве аргумента другую функцию и возвращает ее дополненную версию." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c88b7ed", + "metadata": {}, + "outputs": [], + "source": [ + "# Конструкция @decorator\n", + "\n", + "\n", + "@simple_decorator\n", + "def say_hi() -> None:\n", + " \"\"\"Say hi greeting.\"\"\"\n", + " print(\"Снова, привет!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "c1f5d0d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Текст до вызова функции func().\n", + "Снова, привет!\n", + "Текст после вызова функции func().\n" + ] + } + ], + "source": [ + "say_hi()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3248c113", + "metadata": {}, + "outputs": [], + "source": [ + "# Функции с аргументами\n", + "# Рассмотрим функцию с аргументом и применим к ней simple_decorator().\n", + "#\n", + "# @simple_decorator\n", + "# def say_hello_with_name(name):\n", + "# print(f'Привет, {name}!')\n", + "#\n", + "# При попытке вызова декорируемой функции произойдет ошибка,\n", + "# поскольку внутренняя функция wrapper() не принимает аргументов." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "6ca8100c", + "metadata": {}, + "outputs": [], + "source": [ + "# Исправим это.\n", + "\n", + "\n", + "def decorate_with_name_arg(func: Callable[[str], None]) -> Callable[[str], None]:\n", + " \"\"\"Decorate function with name argument.\"\"\"\n", + "\n", + " def wrapper(name: str) -> None:\n", + " \"\"\"Execute wrapped function.\"\"\"\n", + " print(\"Текст до вызова функции func().\")\n", + " func(name)\n", + " print(\"Текст после вызова функции func().\")\n", + "\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04371d55", + "metadata": {}, + "outputs": [], + "source": [ + "@decorate_with_name_arg\n", + "def say_hello_with_names(name: str) -> None:\n", + " \"\"\"Say hello with person name.\"\"\"\n", + " print(f\"Привет, {name}!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a42db3dc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Текст до вызова функции func().\n", + "Привет, Алексей!\n", + "Текст после вызова функции func().\n" + ] + } + ], + "source": [ + "say_hello_with_names(\"Алексей\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "910acf5b", + "metadata": {}, + "outputs": [], + "source": [ + "# Мы также можем передать во внутреннюю функцию произвольное\n", + "# количество позиционных (*args) и именованных (**kwargs) аргументов.\n", + "\n", + "ParamSpecVar = ParamSpec(\"ParamSpecVar\") # Аргументы функции\n", + "ReturnTypeVar = TypeVar(\"ReturnTypeVar\") # Возвращаемое значение\n", + "\n", + "\n", + "def decorate_with_args(\n", + " func: Callable[ParamSpecVar, ReturnTypeVar],\n", + ") -> Callable[ParamSpecVar, ReturnTypeVar]:\n", + " \"\"\"Decorate with args.\"\"\"\n", + "\n", + " def wrapper(\n", + " *args: ParamSpecVar.args, **kwargs: ParamSpecVar.kwargs\n", + " ) -> ReturnTypeVar:\n", + " print(\"Текст до вызова функции func().\")\n", + " result_dec = func(*args, **kwargs)\n", + " print(\"Текст после вызова функции func().\")\n", + " return result_dec\n", + "\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "14b6a2c6", + "metadata": {}, + "outputs": [], + "source": [ + "@decorate_with_args\n", + "def say_hello_with_argument(name: str) -> None:\n", + " \"\"\"Say hello with argument.\"\"\"\n", + " print(f\"Привет, {name}!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "ad4cf1b1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Текст до вызова функции func().\n", + "Привет, Алексей!\n", + "Текст после вызова функции func().\n" + ] + } + ], + "source": [ + "say_hello_with_argument(\"Алексей\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53c74238", + "metadata": {}, + "outputs": [], + "source": [ + "# Возвращение значения декорируемой функции\n", + "# Объявим функцию return_name_with_decorator и декорируем ее с помощью\n", + "# decorate_with_return().\n", + "\n", + "FunctionArgs = ParamSpec(\"FunctionArgs\") # Аргументы функции\n", + "ReturnArgs = TypeVar(\"ReturnArgs\") # Возвращаемое значение\n", + "\n", + "\n", + "def decorate_with_return(\n", + " func: Callable[FunctionArgs, ReturnArgs],\n", + ") -> Callable[FunctionArgs, ReturnArgs]:\n", + " \"\"\"Decorate function that returns value.\"\"\"\n", + "\n", + " def wrapper(*args: FunctionArgs.args, **kwargs: FunctionArgs.kwargs) -> ReturnArgs:\n", + " \"\"\"Execute wrapped function.\"\"\"\n", + " print(\"Текст внутренней функции.\")\n", + " return_value = func(*args, **kwargs) # Тип выведется автоматически\n", + " return return_value\n", + "\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "7a9fd0d6", + "metadata": {}, + "outputs": [], + "source": [ + "@decorate_with_args\n", + "def return_name_with_decorator(name: str) -> str:\n", + " \"\"\"Return provided name.\"\"\"\n", + " return name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5626b7d4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Текст до вызова функции func().\n", + "Текст после вызова функции func().\n" + ] + } + ], + "source": [ + "# Посмотрим, какое значение вернула эта функция.\n", + "\n", + "\n", + "returned_value: str = return_name_with_decorator(\"Алексей\")" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "4252be92", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "None\n" + ] + } + ], + "source": [ + "print(returned_value)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e1c31e8", + "metadata": {}, + "outputs": [], + "source": [ + "# Декоратор @functools.wraps\n", + "# Питон содержит инструменты для интроспекции (introspection),\n", + "# которые позволяют исследовать уже созданный объект" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "ea27baae", + "metadata": {}, + "outputs": [], + "source": [ + "def square(value: int) -> int:\n", + " \"\"\"Square a number.\"\"\"\n", + " return value * value" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "2c8acb1b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(\"square\", \"Square a number.\")" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(square.__name__, square.__doc__)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "811ca63d", + "metadata": {}, + "outputs": [], + "source": [ + "# При использовании декоратора мы по сути заменяем исходную\n", + "# декорируемую функцию на внутреннюю замыкающую функцию.\n", + "\n", + "WrapperArg = ParamSpec(\"WrapperArg\")\n", + "\n", + "\n", + "def repeat_twice_no_meta(\n", + " func: Callable[WrapperArg, None],\n", + ") -> Callable[WrapperArg, None]:\n", + " \"\"\"Repeat function execution twice.\"\"\"\n", + "\n", + " def wrapper(*args: WrapperArg.args, **kwargs: WrapperArg.kwargs) -> None:\n", + " \"\"\"Execute wrapped function.\"\"\"\n", + " func(*args, **kwargs)\n", + " func(*args, **kwargs)\n", + "\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "e882de72", + "metadata": {}, + "outputs": [], + "source": [ + "@repeat_twice_no_meta\n", + "def square_repeated(value: int) -> None:\n", + " \"\"\"Square a number.\"\"\"\n", + " print(value * value)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "5cc1be68", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "9\n", + "9\n" + ] + } + ], + "source": [ + "square_repeated(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "99b04b0e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(\"wrapper\", None)" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Как следствие, инструменты интроспекции не видят\n", + "# декорируемой функции.\n", + "print(square_repeated.__name__, square_repeated.__doc__)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1695d3df", + "metadata": {}, + "outputs": [], + "source": [ + "# Декоратор @functools.wraps модуля functools позволяет\n", + "# сохранить метаданные декорируемого объекта.\n", + "\n", + "\n", + "RTVar = TypeVar(\"RTVar\")\n", + "Params = ParamSpec(\"Params\")\n", + "\n", + "\n", + "def repeat_twice(\n", + " func: Callable[Params, RTVar],\n", + ") -> Callable[Params, RTVar]:\n", + " \"\"\"Repeat function execution twice.\"\"\"\n", + "\n", + " @functools.wraps(func)\n", + " def wrapper(*args: Params.args, **kwargs: Params.kwargs) -> RTVar:\n", + " \"\"\"Execute wrapped function.\"\"\"\n", + " func(*args, **kwargs)\n", + " return func(*args, **kwargs)\n", + "\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "20a26be9", + "metadata": {}, + "outputs": [], + "source": [ + "@repeat_twice\n", + "def square_with_metadata(value: int) -> None:\n", + " \"\"\"Square a number.\"\"\"\n", + " print(value * value)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "ec355f75", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(\"square_with_metadata\", \"Square a number.\")" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(square_with_metadata.__name__, square_with_metadata.__doc__)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62981391", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Атрибут __wrapped__ ссылается на исходную функцию\n", + "# до применения декоратора.\n", + "\n", + "# print(square_with_metadata.__wrapped__)\n", + "# " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c5086003", + "metadata": {}, + "outputs": [], + "source": [ + "# Можно также воспользоваться функцией functools.update_wrapper().\n", + "\n", + "\n", + "ParamUpdateVar = ParamSpec(\"ParamUpdateVar\")\n", + "\n", + "\n", + "def repeat_twice_update_wrapper(\n", + " func: Callable[ParamUpdateVar, None],\n", + ") -> Callable[ParamUpdateVar, None]:\n", + " \"\"\"Repeat function execution twice using update_wrapper.\"\"\"\n", + "\n", + " def wrapper(*args: ParamUpdateVar.args, **kwargs: ParamUpdateVar.kwargs) -> None:\n", + " \"\"\"Execute wrapped function.\"\"\"\n", + " func(*args, **kwargs)\n", + " func(*args, **kwargs)\n", + "\n", + " update_wrapper(wrapper, func)\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b2d8dc66", + "metadata": {}, + "outputs": [], + "source": [ + "@repeat_twice_update_wrapper\n", + "def power(base: int, exponent: int) -> None:\n", + " \"\"\"Raise value to a power.\"\"\"\n", + " print(base**exponent)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f1ab79a4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8\n", + "8\n" + ] + } + ], + "source": [ + "power(2, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "64647f2e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Raise value to a power.\"" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(power.__doc__)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3ca73647", + "metadata": {}, + "outputs": [], + "source": [ + "# Примеры декораторов\n", + "# Декоратор можно использовать для выведения (и записи)\n", + "# информации о вызове функции (логирования).\n", + "\n", + "\n", + "ParamLogVar = ParamSpec(\"ParamLogVar\")\n", + "ReturnLog = TypeVar(\"ReturnLog\") # Для возвращаемого значения\n", + "\n", + "\n", + "def logging_decorator(\n", + " func: Callable[ParamLogVar, ReturnLog],\n", + ") -> Callable[ParamLogVar, ReturnLog]:\n", + " \"\"\"Decorate function for logging function calls.\"\"\"\n", + "\n", + " @functools.wraps(func)\n", + " def wrapper(*args: ParamLogVar.args, **kwargs: ParamLogVar.kwargs) -> ReturnLog:\n", + " \"\"\"Execute wrapped function.\"\"\"\n", + " print(f\"Calling {func.__name__} with args: {args}, kwargs: {kwargs}\")\n", + " result_log = func(*args, **kwargs)\n", + " print(f\"{func.__name__} returned: {result_log}\")\n", + " return result_log\n", + "\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39cc8cc3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Calling power_logged with args: (5, 3), kwargs: {}\n", + "power_logged returned: 125\n", + "125\n" + ] + } + ], + "source": [ + "@logging_decorator\n", + "def power_logged(base: int, exponent: int) -> int:\n", + " \"\"\"Calculate power.\"\"\"\n", + " return int(base**exponent)\n", + "\n", + "\n", + "print(power_logged(5, 3))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc73fbde", + "metadata": {}, + "outputs": [], + "source": [ + "ParamTimeVar = ParamSpec(\"ParamTimeVar\")\n", + "ReturnTimeLog = TypeVar(\"ReturnTimeLog\")\n", + "\n", + "\n", + "def timer_decorator(\n", + " func: Callable[ParamTimeVar, ReturnTimeLog],\n", + ") -> Callable[ParamTimeVar, ReturnTimeLog]:\n", + " \"\"\"Decorate timer.\"\"\"\n", + "\n", + " @functools.wraps(func)\n", + " def wrapper(\n", + " *args: ParamTimeVar.args, **kwargs: ParamTimeVar.kwargs\n", + " ) -> ReturnTimeLog:\n", + " start_time = time.time()\n", + " result_time: ReturnTimeLog = func(*args, **kwargs)\n", + " end_time = time.time()\n", + " print(f\"{func.__name__} executed in {end_time - start_time:.4f} seconds\")\n", + " return result_time\n", + "\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1376ebc7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delayed_function executed in 2.0004 seconds\n" + ] + }, + { + "data": { + "text/plain": [ + "'execution completed'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "@timer_decorator\n", + "def delayed_function(timer: float) -> str:\n", + " \"\"\"Delay function.\"\"\"\n", + " time.sleep(timer)\n", + " return \"execution completed\"\n", + "\n", + "\n", + "delayed_function(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3f773a7", + "metadata": {}, + "outputs": [], + "source": [ + "# Типы методов\n", + "# Методы экземпляра\n", + "\n", + "\n", + "class CatClass:\n", + " \"\"\"A simple cat class.\"\"\"\n", + "\n", + " def __init__(self, color: str) -> None:\n", + " \"\"\"Initialize cat instance.\"\"\"\n", + " self.color = color\n", + " self.type_ = \"cat\"\n", + "\n", + " def info(self) -> None:\n", + " \"\"\"Print cat info.\"\"\"\n", + " print(self.color, self.type_, sep=\", \")" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "eeaea115", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "black, cat\n" + ] + } + ], + "source": [ + "cat = CatClass(color=\"black\")\n", + "cat.info()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5a3b154", + "metadata": {}, + "outputs": [], + "source": [ + "# При этом поскольку это атрибуты и методы экземляра класса,\n", + "# применить их к самому классу мы не можем.\n", + "#\n", + "# CatClass.info() # будет ошибка" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "e26052f8", + "metadata": {}, + "outputs": [], + "source": [ + "# Переменные класса (class variable) и методы класса\n", + "# (class method) позволяют обратиться к\n", + "# самому классу без создания экземпляра.\n", + "\n", + "\n", + "class CatClassWithSpecies:\n", + " \"\"\"Cat class with species class method.\"\"\"\n", + "\n", + " species: str = \"кошка\" # переменная класса доступна всем экземлярам\n", + "\n", + " def __init__(self, color: str) -> None:\n", + " \"\"\"Initialize cat instance.\"\"\"\n", + " self.color = color\n", + "\n", + " def info(self) -> None:\n", + " \"\"\"Print cat color.\"\"\"\n", + " print(self.color)\n", + "\n", + " @classmethod\n", + " def get_species(cls) -> None:\n", + " \"\"\"Get species of the cat.\"\"\"\n", + " print(cls.species)" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "cd66ee52", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"кошка\"" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(CatClassWithSpecies.species)" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "6b582b4c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "кошка\n" + ] + } + ], + "source": [ + "CatClassWithSpecies.get_species()" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "834b9173", + "metadata": {}, + "outputs": [], + "source": [ + "# Статические методы (static method) не имеют доступа\n", + "# ни к атрибутам экземпляра, ни к атрибутам класса.\n", + "#\n", + "# Чаще всего статические методы — это служебные методы,\n", + "# логически связанные с функционалом создаваемого класса.\n", + "# В нашем примере создадим статический метод для\n", + "# преобразования веса кошки из килограммов в фунты.\n", + "\n", + "\n", + "class CatClassWithStatic:\n", + " \"\"\"Cat class with static method.\"\"\"\n", + "\n", + " species: str = \"кошка\"\n", + "\n", + " def __init__(self, color: str) -> None:\n", + " \"\"\"Initialize cat instance.\"\"\"\n", + " self.color = color\n", + " self.type_ = \"cat\"\n", + "\n", + " def info(self) -> None:\n", + " \"\"\"Print cat info.\"\"\"\n", + " print(self.color, self.type_)\n", + "\n", + " @classmethod\n", + " def get_species(cls) -> None:\n", + " \"\"\"Get species.\"\"\"\n", + " print(cls.species)\n", + "\n", + " @staticmethod\n", + " def convert_to_pounds(kilograms: float) -> None:\n", + " \"\"\"Convert kilograms to pounds.\"\"\"\n", + " pounds: float = kilograms * 2.205\n", + " print(f\"{kilograms} kg is approximately {pounds} pounds\")" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "53b21afd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4 kg is approximately 8.82 pounds\n" + ] + } + ], + "source": [ + "# Статические методы можно вызвать как из самого класса,\n", + "# так и из экземпляра класса.\n", + "\n", + "\n", + "CatClassWithStatic.convert_to_pounds(4)" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "b7e9dc81", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5 kg is approximately 11.025 pounds\n" + ] + } + ], + "source": [ + "cat_instance: CatClassWithStatic = CatClassWithStatic(\"gray\")\n", + "cat_instance.convert_to_pounds(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87f8fca5", + "metadata": {}, + "outputs": [], + "source": [ + "# Декорирование класса\n", + "# Помимо встроенных декораторов @classmethod и @staticmethod\n", + "# мы можем расширять функционал класса с помощью\n", + "# собственных декораторов.\n", + "\n", + "\n", + "@timer_decorator\n", + "class CatClassDecorated:\n", + " \"\"\"Cat class decorated with timer.\"\"\"\n", + "\n", + " def __init__(self, color: str) -> None:\n", + " \"\"\"Initialize cat instance.\"\"\"\n", + " self.color = color\n", + " self.type_ = \"cat\"\n", + "\n", + " def info(self) -> None:\n", + " \"\"\"Print cat info.\"\"\"\n", + " time.sleep(2)\n", + " print(self.color, self.type_, sep=\", \")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5516b381", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CatClassDecorated executed in 0.0000 seconds\n" + ] + } + ], + "source": [ + "cat_decorated = CatClassDecorated(\"gray\")\n", + "\n", + "# Как мы видим, декоратор «сработал» только при создании\n", + "# экземпляра класса." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "f9fe345f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gray, cat\n" + ] + } + ], + "source": [ + "cat_decorated.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "0023f727", + "metadata": {}, + "outputs": [], + "source": [ + "# Теперь рассмотрим функцию setattr(), которая позволяет\n", + "# добавить к экземпляру класса атрибут и соответствующее\n", + "# значение атрибута.\n", + "\n", + "setattr(cat_decorated, \"weight\", 5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2d0149b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(5, 5)" + ] + }, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Получить значение атрибута можно, обратившись к атрибуту\n", + "# напрямую или через функцию getattr().\n", + "\n", + "weight_value = getattr(cat_decorated, \"weight\")\n", + "print(weight_value, weight_value)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c22e81b", + "metadata": {}, + "outputs": [], + "source": [ + "# Функцию setattr() можно использовать в декораторе и,\n", + "# таким образом, добавить переменную класса.\n", + "\n", + "\n", + "SetVar = TypeVar(\"SetVar\", bound=type)\n", + "\n", + "\n", + "def add_attribute(\n", + " attribute_name: str, attribute_value: object\n", + ") -> Callable[[type], type]:\n", + " \"\"\"Decorate class to add attribute.\"\"\"\n", + "\n", + " def wrapper(cls: type) -> type:\n", + " \"\"\"Execute decorator.\"\"\"\n", + " setattr(cls, attribute_name, attribute_value)\n", + " return cls\n", + "\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb7c186a", + "metadata": {}, + "outputs": [], + "source": [ + "@add_attribute(\"species\", \"кошка\")\n", + "class CatClassWithAttribute:\n", + " \"\"\"Cat class with species attribute.\"\"\"\n", + "\n", + " def __init__(self, color: str) -> None:\n", + " \"\"\"Initialize cat instance.\"\"\"\n", + " self.color = color\n", + " self.type_ = \"cat\"" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "120e483b", + "metadata": {}, + "outputs": [], + "source": [ + "# Несколько декораторов\n", + "# Ничто не мешает использовать несколько декораторов.\n", + "\n", + "\n", + "@logging_decorator\n", + "@timer_decorator\n", + "def delayed_function_decorated(delay_time: float) -> str:\n", + " \"\"\"Execute after delay with logging and timer.\"\"\"\n", + " time.sleep(delay_time)\n", + " return \"execution completed\"" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "0c9ffc9d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Calling delayed_function_decorated with args: (2,), kwargs: {}\n", + "delayed_function_decorated executed in 2.0007 seconds\n", + "delayed_function_decorated returned: execution completed\n" + ] + }, + { + "data": { + "text/plain": [ + "\"execution completed\"" + ] + }, + "execution_count": 89, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(delayed_function_decorated(2))" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "ffc1ae18", + "metadata": {}, + "outputs": [], + "source": [ + "# Вместо синтаксического сахара можно записать функцию в\n", + "# переменную, последовательно применив сначала один декоратор,\n", + "# а затем второй.\n", + "\n", + "\n", + "# не забудем заново объявить функцию без декораторов\n", + "def delayed_function_manual(delay_time: float) -> str:\n", + " \"\"\"Execute after delay.\"\"\"\n", + " time.sleep(delay_time)\n", + " return \"execution completed\"" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "cf131259", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Calling wrapper with args: (2,), kwargs: {}\n", + "delayed_function_manual executed in 2.0000 seconds\n", + "wrapper returned: execution completed\n" + ] + }, + { + "data": { + "text/plain": [ + "\"execution completed\"" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "delayed_function_manual = logging_decorator(timer_decorator(delayed_function_manual))\n", + "print(delayed_function_manual(2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a5c9c3d", + "metadata": {}, + "outputs": [], + "source": [ + "# Декораторы с аргументами\n", + "# Иногда бывает полезно передать декоратору некоторый аргумент.\n", + "# Например, модифицируем декоратор @repeat_twice\n", + "# таким образом, чтобы он вызывал декорируемую функцию\n", + "# заданное параметром количество раз." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c56ba1ba", + "metadata": {}, + "outputs": [], + "source": [ + "ParamRepeatVar = ParamSpec(\"ParamRepeatVar\")\n", + "ReturnRepeatLog = TypeVar(\"ReturnRepeatLog\")\n", + "\n", + "\n", + "def repeat(\n", + " n_times: int,\n", + ") -> Callable[\n", + " [Callable[ParamRepeatVar, ReturnRepeatLog]],\n", + " Callable[ParamRepeatVar, ReturnRepeatLog],\n", + "]:\n", + " \"\"\"Decorate function factory for repeating function calls.\"\"\"\n", + "\n", + " def inner_decorator(\n", + " func: Callable[ParamRepeatVar, ReturnRepeatLog],\n", + " ) -> Callable[ParamRepeatVar, ReturnRepeatLog]:\n", + " \"\"\"Inner decorator function.\"\"\"\n", + "\n", + " @functools.wraps(func)\n", + " def wrapper(\n", + " *args: ParamRepeatVar.args, **kwargs: ParamRepeatVar.kwargs\n", + " ) -> ReturnRepeatLog:\n", + " \"\"\"Execute wrapped function.\"\"\"\n", + " for _ in range(n_times):\n", + " func(*args, **kwargs)\n", + " return func(*args, **kwargs) # возвращаем последний результат\n", + "\n", + " return wrapper\n", + "\n", + " return inner_decorator" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "562e0b0b", + "metadata": {}, + "outputs": [], + "source": [ + "@repeat(n_times=3)\n", + "def say_hello_repeated(name: str) -> None:\n", + " \"\"\"Say hello with repeating.\"\"\"\n", + " print(f\"Привет, {name}!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "2ee8bba8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Привет, Алексей!\n", + "Привет, Алексей!\n", + "Привет, Алексей!\n" + ] + } + ], + "source": [ + "say_hello_repeated(\"Алексей\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_12_decorators.py b/python/makarov/chapter_12_decorators.py new file mode 100644 index 00000000..d1ce33b7 --- /dev/null +++ b/python/makarov/chapter_12_decorators.py @@ -0,0 +1,791 @@ +"""Decorators.""" + +# + +# Декораторы +# Декораторы - это функции, которые принимают другую +# функцию в качестве аргумента +# расширяют её функциональность без изменения её +# кода и возвращают новую функцию. + +# + +# Объекты первого класса +# +# Функции в Питоне представляют собой объекты первого класса +# (first class objects), что означает, что их можно присваивать +# переменной, возвращать из функции или передавать другой +# функции в качестве аргумента. + +# + +import functools +import time + +# Можно также воспользоваться функцией functools.update_wrapper(). +from functools import update_wrapper + +# Присвоение функции переменной +from typing import Callable, ParamSpec, TypeVar + + +def say_hello(name: str) -> None: + """Print greeting message.""" + print(f"Привет, {name}!") + + +# - + +# присвоим эту функцию переменной (без скобок) +say_hello_function = say_hello +# вызовем функцию из новой переменной +say_hello_function("Алексей") + +# + +# Передача функции в качестве аргумента + + +Rval = TypeVar("Rval") + + +def calculate_result( + operation: Callable[[int, int], Rval], first: int, second: int +) -> Rval: + """Calculate using provided operation.""" + return operation(first, second) + + +def add(first: int, second: int) -> int: + """Add two numbers.""" + return first + second + + +def subtract(first: int, second: int) -> int: + """Subtract two numbers.""" + return first - second + + +def multiply(first: int, second: int) -> int: + """Multiply two numbers.""" + return first * second + + +def divide(first: int, second: int) -> float: + """Divide two numbers.""" + return first / second + + +# + +# Разделим единицу на три. + + +result = calculate_result(divide, 1, 3) +print(result) # 0.3333333333333333 + +# + +# Внутрение функции +# Внутренние (inner) или вложенные (nested) функции +# представляют собой функции, объявленные и вызванные внутри других функций. +# Вызов внутренней функции + + +def outer() -> None: + """Call outer and inner functions.""" + print("Вызов внешней функции.") + + # обратите внимание, мы объявляем, а затем + def inner() -> None: + """Call inner function.""" + print("Вызов внутренней функции.") + + # вызываем внутреннюю функцию + inner() + + +# - + +outer() + +# + +# Вызвать внутреннюю функцию не получится. + +# inner() # Вызвать внутреннюю функцию не получится. + +# + +# Возвращение функции из функции +# Функция может возвращать другую функцию. В примере ниже функция +# create_multiplier() создает множитель (factor) для передаваемого во +# внуреннюю функцию multiplier() числа (number). + + +def create_multiplier(factor: int) -> Callable[[int], int]: + """Create multiplier function.""" + + def multiplier(number: int) -> int: + """Multiply number by factor.""" + return number * factor + + return multiplier + + +# + +# Создадим две ссылки на внутреннюю функцию multiplier(), +# передав ей в качестве параметра множитель, +# равный соответственно двум и трем. + + +double = create_multiplier(factor=2) +triple = create_multiplier(factor=3) + +# + +# Умножим число два на каждый из множителей. + +result_double: int = double(2) +result_triple: int = triple(2) +print(result_double, result_triple) + +# + +# Заметим, что код в примере выше можно сократить с помощью +# lambda-функции. + + +def create_multiplier_lambda(factor: int) -> Callable[[int], int]: + """Create multiplier function using lambda.""" + return lambda number: factor * number + + +# - + +triple = create_multiplier_lambda(factor=3) +print(triple(2)) + +# + +# Замыкание +# +# Замыкание (closure) — это внутренняя функция, которая сохраняет доступ +# к переменным из внешней (окружающей) функции даже после завершения +# выполнения внешней функции. + +# + +# Знакомство с декораторами +# Простой декоратор + + +T = TypeVar("T", bound=Callable[[], None]) + + +def simple_decorator(func: T) -> T: + """Wrap function with simple decorator.""" + + def wrapper() -> None: + print(f"{func.__name__} called.") + func() + print("simple_decorator finished.") + + return wrapper # type: ignore[return-value] + + +def say_hello_simple() -> None: + """Greet user.""" + print("Привет!") + + +# - + +say_hello_simple = simple_decorator(say_hello_simple) + +# Вызовем функцию say_hello(). +say_hello_simple() + +# + +# Таким образом, декоратор — это функция, которая принимает в +# качестве аргумента другую функцию и возвращает ее дополненную версию. + +# + +# Конструкция @decorator + + +@simple_decorator +def say_hi() -> None: + """Say hi greeting.""" + print("Снова, привет!") + + +# - + +say_hi() + +# + +# Функции с аргументами +# Рассмотрим функцию с аргументом и применим к ней simple_decorator(). +# +# @simple_decorator +# def say_hello_with_name(name): +# print(f'Привет, {name}!') +# +# При попытке вызова декорируемой функции произойдет ошибка, +# поскольку внутренняя функция wrapper() не принимает аргументов. + +# + +# Исправим это. + + +def decorate_with_name_arg(func: Callable[[str], None]) -> Callable[[str], None]: + """Decorate function with name argument.""" + + def wrapper(name: str) -> None: + """Execute wrapped function.""" + print("Текст до вызова функции func().") + func(name) + print("Текст после вызова функции func().") + + return wrapper + + +# - + + +@decorate_with_name_arg +def say_hello_with_names(name: str) -> None: + """Say hello with person name.""" + print(f"Привет, {name}!") + + +say_hello_with_names("Алексей") + +# + +# Мы также можем передать во внутреннюю функцию произвольное +# количество позиционных (*args) и именованных (**kwargs) аргументов. + +ParamSpecVar = ParamSpec("ParamSpecVar") # Аргументы функции +ReturnTypeVar = TypeVar("ReturnTypeVar") # Возвращаемое значение + + +def decorate_with_args( + func: Callable[ParamSpecVar, ReturnTypeVar], +) -> Callable[ParamSpecVar, ReturnTypeVar]: + """Decorate with args.""" + + def wrapper( + *args: ParamSpecVar.args, **kwargs: ParamSpecVar.kwargs + ) -> ReturnTypeVar: + print("Текст до вызова функции func().") + result_dec = func(*args, **kwargs) + print("Текст после вызова функции func().") + return result_dec + + return wrapper + + +# - + + +@decorate_with_args +def say_hello_with_argument(name: str) -> None: + """Say hello with argument.""" + print(f"Привет, {name}!") + + +say_hello_with_argument("Алексей") + +# + +# Возвращение значения декорируемой функции +# Объявим функцию return_name_with_decorator и декорируем ее с помощью +# decorate_with_return(). + +FunctionArgs = ParamSpec("FunctionArgs") # Аргументы функции +ReturnArgs = TypeVar("ReturnArgs") # Возвращаемое значение + + +def decorate_with_return( + func: Callable[FunctionArgs, ReturnArgs], +) -> Callable[FunctionArgs, ReturnArgs]: + """Decorate function that returns value.""" + + def wrapper(*args: FunctionArgs.args, **kwargs: FunctionArgs.kwargs) -> ReturnArgs: + """Execute wrapped function.""" + print("Текст внутренней функции.") + return_value = func(*args, **kwargs) # Тип выведется автоматически + return return_value + + return wrapper + + +# - + + +@decorate_with_args +def return_name_with_decorator(name: str) -> str: + """Return provided name.""" + return name + + +# + +# Посмотрим, какое значение вернула эта функция. + + +returned_value: str = return_name_with_decorator("Алексей") +# - + +print(returned_value) + + +# + +# Декоратор @functools.wraps +# Питон содержит инструменты для интроспекции (introspection), +# которые позволяют исследовать уже созданный объект +# - + + +def square(value: int) -> int: + """Square a number.""" + return value * value + + +print(square.__name__, square.__doc__) + +# + +# При использовании декоратора мы по сути заменяем исходную +# декорируемую функцию на внутреннюю замыкающую функцию. + +WrapperArg = ParamSpec("WrapperArg") + + +def repeat_twice_no_meta( + func: Callable[WrapperArg, None], +) -> Callable[WrapperArg, None]: + """Repeat function execution twice.""" + + def wrapper(*args: WrapperArg.args, **kwargs: WrapperArg.kwargs) -> None: + """Execute wrapped function.""" + func(*args, **kwargs) + func(*args, **kwargs) + + return wrapper + + +# - + + +@repeat_twice_no_meta +def square_repeated(value: int) -> None: + """Square a number.""" + print(value * value) + + +square_repeated(3) + +# Как следствие, инструменты интроспекции не видят +# декорируемой функции. +print(square_repeated.__name__, square_repeated.__doc__) + +# + +# Декоратор @functools.wraps модуля functools позволяет +# сохранить метаданные декорируемого объекта. + + +RTVar = TypeVar("RTVar") +Params = ParamSpec("Params") + + +def repeat_twice( + func: Callable[Params, RTVar], +) -> Callable[Params, RTVar]: + """Repeat function execution twice.""" + + @functools.wraps(func) + def wrapper(*args: Params.args, **kwargs: Params.kwargs) -> RTVar: + """Execute wrapped function.""" + func(*args, **kwargs) + return func(*args, **kwargs) + + return wrapper + + +# - + + +@repeat_twice +def square_with_metadata(value: int) -> None: + """Square a number.""" + print(value * value) + + +print(square_with_metadata.__name__, square_with_metadata.__doc__) + +# + +# Атрибут __wrapped__ ссылается на исходную функцию +# до применения декоратора. + +# print(square_with_metadata.__wrapped__) +# + +# + +# Можно также воспользоваться функцией functools.update_wrapper(). + + +ParamUpdateVar = ParamSpec("ParamUpdateVar") + + +def repeat_twice_update_wrapper( + func: Callable[ParamUpdateVar, None], +) -> Callable[ParamUpdateVar, None]: + """Repeat function execution twice using update_wrapper.""" + + def wrapper(*args: ParamUpdateVar.args, **kwargs: ParamUpdateVar.kwargs) -> None: + """Execute wrapped function.""" + func(*args, **kwargs) + func(*args, **kwargs) + + update_wrapper(wrapper, func) + return wrapper + + +# - + + +@repeat_twice_update_wrapper +def power(base: int, exponent: int) -> None: + """Raise value to a power.""" + print(base**exponent) + + +power(2, 3) + +print(power.__doc__) + +# + +# Примеры декораторов +# Декоратор можно использовать для выведения (и записи) +# информации о вызове функции (логирования). + + +ParamLogVar = ParamSpec("ParamLogVar") +ReturnLog = TypeVar("ReturnLog") # Для возвращаемого значения + + +def logging_decorator( + func: Callable[ParamLogVar, ReturnLog], +) -> Callable[ParamLogVar, ReturnLog]: + """Decorate function for logging function calls.""" + + @functools.wraps(func) + def wrapper(*args: ParamLogVar.args, **kwargs: ParamLogVar.kwargs) -> ReturnLog: + """Execute wrapped function.""" + print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}") + result_log = func(*args, **kwargs) + print(f"{func.__name__} returned: {result_log}") + return result_log + + return wrapper + + +# + +@logging_decorator +def power_logged(base: int, exponent: int) -> int: + """Calculate power.""" + return int(base**exponent) + + +print(power_logged(5, 3)) + +# + +ParamTimeVar = ParamSpec("ParamTimeVar") +ReturnTimeLog = TypeVar("ReturnTimeLog") + + +def timer_decorator( + func: Callable[ParamTimeVar, ReturnTimeLog], +) -> Callable[ParamTimeVar, ReturnTimeLog]: + """Decorate timer.""" + + @functools.wraps(func) + def wrapper( + *args: ParamTimeVar.args, **kwargs: ParamTimeVar.kwargs + ) -> ReturnTimeLog: + start_time = time.time() + result_time: ReturnTimeLog = func(*args, **kwargs) + end_time = time.time() + print(f"{func.__name__} executed in {end_time - start_time:.4f} seconds") + return result_time + + return wrapper + + +# + +@timer_decorator +def delayed_function(timer: float) -> str: + """Delay function.""" + time.sleep(timer) + return "execution completed" + + +delayed_function(2) + +# + +# Типы методов +# Методы экземпляра + + +class CatClass: + """A simple cat class.""" + + def __init__(self, color: str) -> None: + """Initialize cat instance.""" + self.color = color + self.type_ = "cat" + + def info(self) -> None: + """Print cat info.""" + print(self.color, self.type_, sep=", ") + + +# - + +cat = CatClass(color="black") +cat.info() + +# + +# При этом поскольку это атрибуты и методы экземляра класса, +# применить их к самому классу мы не можем. +# +# CatClass.info() # будет ошибка + +# + +# Переменные класса (class variable) и методы класса +# (class method) позволяют обратиться к +# самому классу без создания экземпляра. + + +class CatClassWithSpecies: + """Cat class with species class method.""" + + species: str = "кошка" # переменная класса доступна всем экземлярам + + def __init__(self, color: str) -> None: + """Initialize cat instance.""" + self.color = color + + def info(self) -> None: + """Print cat color.""" + print(self.color) + + @classmethod + def get_species(cls) -> None: + """Get species of the cat.""" + print(cls.species) + + +# - + +print(CatClassWithSpecies.species) + +CatClassWithSpecies.get_species() + +# + +# Статические методы (static method) не имеют доступа +# ни к атрибутам экземпляра, ни к атрибутам класса. +# +# Чаще всего статические методы — это служебные методы, +# логически связанные с функционалом создаваемого класса. +# В нашем примере создадим статический метод для +# преобразования веса кошки из килограммов в фунты. + + +class CatClassWithStatic: + """Cat class with static method.""" + + species: str = "кошка" + + def __init__(self, color: str) -> None: + """Initialize cat instance.""" + self.color = color + self.type_ = "cat" + + def info(self) -> None: + """Print cat info.""" + print(self.color, self.type_) + + @classmethod + def get_species(cls) -> None: + """Get species.""" + print(cls.species) + + @staticmethod + def convert_to_pounds(kilograms: float) -> None: + """Convert kilograms to pounds.""" + pounds: float = kilograms * 2.205 + print(f"{kilograms} kg is approximately {pounds} pounds") + + +# + +# Статические методы можно вызвать как из самого класса, +# так и из экземпляра класса. + + +CatClassWithStatic.convert_to_pounds(4) +# - + +cat_instance: CatClassWithStatic = CatClassWithStatic("gray") +cat_instance.convert_to_pounds(5) + +# + +# Декорирование класса +# Помимо встроенных декораторов @classmethod и @staticmethod +# мы можем расширять функционал класса с помощью +# собственных декораторов. + + +@timer_decorator +class CatClassDecorated: + """Cat class decorated with timer.""" + + def __init__(self, color: str) -> None: + """Initialize cat instance.""" + self.color = color + self.type_ = "cat" + + def info(self) -> None: + """Print cat info.""" + time.sleep(2) + print(self.color, self.type_, sep=", ") + + +# + +cat_decorated = CatClassDecorated("gray") + +# Как мы видим, декоратор «сработал» только при создании +# экземпляра класса. +# - + +cat_decorated.info() + +# + +# Теперь рассмотрим функцию setattr(), которая позволяет +# добавить к экземпляру класса атрибут и соответствующее +# значение атрибута. + +setattr(cat_decorated, "weight", 5) + +# + +# Получить значение атрибута можно, обратившись к атрибуту +# напрямую или через функцию getattr(). + +weight_value = getattr(cat_decorated, "weight") +print(weight_value, weight_value) + +# + +# Функцию setattr() можно использовать в декораторе и, +# таким образом, добавить переменную класса. + + +SetVar = TypeVar("SetVar", bound=type) + + +def add_attribute( + attribute_name: str, attribute_value: object +) -> Callable[[type], type]: + """Decorate class to add attribute.""" + + def wrapper(cls: type) -> type: + """Execute decorator.""" + setattr(cls, attribute_name, attribute_value) + return cls + + return wrapper + + +# - + + +@add_attribute("species", "кошка") +class CatClassWithAttribute: + """Cat class with species attribute.""" + + def __init__(self, color: str) -> None: + """Initialize cat instance.""" + self.color = color + self.type_ = "cat" + + +# + +# Несколько декораторов +# Ничто не мешает использовать несколько декораторов. + + +@logging_decorator +@timer_decorator +def delayed_function_decorated(delay_time: float) -> str: + """Execute after delay with logging and timer.""" + time.sleep(delay_time) + return "execution completed" + + +# - + +print(delayed_function_decorated(2)) + +# + +# Вместо синтаксического сахара можно записать функцию в +# переменную, последовательно применив сначала один декоратор, +# а затем второй. + + +# не забудем заново объявить функцию без декораторов +def delayed_function_manual(delay_time: float) -> str: + """Execute after delay.""" + time.sleep(delay_time) + return "execution completed" + + +# - + +delayed_function_manual = logging_decorator(timer_decorator(delayed_function_manual)) +print(delayed_function_manual(2)) + +# + +# Декораторы с аргументами +# Иногда бывает полезно передать декоратору некоторый аргумент. +# Например, модифицируем декоратор @repeat_twice +# таким образом, чтобы он вызывал декорируемую функцию +# заданное параметром количество раз. + +# + +ParamRepeatVar = ParamSpec("ParamRepeatVar") +ReturnRepeatLog = TypeVar("ReturnRepeatLog") + + +def repeat( + n_times: int, +) -> Callable[ + [Callable[ParamRepeatVar, ReturnRepeatLog]], + Callable[ParamRepeatVar, ReturnRepeatLog], +]: + """Decorate function factory for repeating function calls.""" + + def inner_decorator( + func: Callable[ParamRepeatVar, ReturnRepeatLog], + ) -> Callable[ParamRepeatVar, ReturnRepeatLog]: + """Inner decorator function.""" + + @functools.wraps(func) + def wrapper( + *args: ParamRepeatVar.args, **kwargs: ParamRepeatVar.kwargs + ) -> ReturnRepeatLog: + """Execute wrapped function.""" + for _ in range(n_times): + func(*args, **kwargs) + return func(*args, **kwargs) # возвращаем последний результат + + return wrapper + + return inner_decorator + + +# - + + +@repeat(n_times=3) +def say_hello_repeated(name: str) -> None: + """Say hello with repeating.""" + print(f"Привет, {name}!") + + +say_hello_repeated("Алексей") diff --git a/python/makarov/chapter_3_if_and_loops.ipynb b/python/makarov/chapter_3_if_and_loops.ipynb new file mode 100644 index 00000000..0be3eba0 --- /dev/null +++ b/python/makarov/chapter_3_if_and_loops.ipynb @@ -0,0 +1,424 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "fa2286f9", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Условия и циклы. Продолжение.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92fdfa77", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Dict, List, Union" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5551ff67", + "metadata": {}, + "outputs": [], + "source": [ + "# Еще раз про условия с if\n", + "# напишем программу, которая разобьет все числа на малые,\n", + "# средние и большие\n", + "\n", + "# запросим число у пользователя и преобразуем в тип int\n", + "x_var = int(input())\n", + "\n", + "# и наконец классифицируем число\n", + "if x_var < 10:\n", + " print(\"Small number\")\n", + "elif x_var < 100:\n", + " print(\"Medium number\")\n", + "else:\n", + " print(\"Large number\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "694422dd", + "metadata": {}, + "outputs": [], + "source": [ + "# Несколько условий в одном выражении с операторами and или or\n", + "\n", + "x_var = int(input())\n", + "\n", + "if x_var < 10 or x_var > 100:\n", + " print(\"Small or large number\")\n", + "else:\n", + " print(\"Medium number\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87ce0540", + "metadata": {}, + "outputs": [], + "source": [ + "# Проверка вхождения элемента в объект с in / not in\n", + "\n", + "# можно проверить вхождение слова в строку\n", + "sentence = \"To be, or not to be, that is the question\"\n", + "word = \"question\"\n", + "if word in sentence:\n", + " print(f\"Слово {word} найдено.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53edbb31", + "metadata": {}, + "outputs": [], + "source": [ + "# или отсутствие элемента в списке\n", + "my_list = [1, 2, 3, 4, 5]\n", + "number = 10\n", + "if number not in my_list:\n", + " print(f\"Элемент {number} отсутствует в списке.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d86b8f9a", + "metadata": {}, + "outputs": [], + "source": [ + "# кроме того, можно проверить вхождение ключа и значения в словарь\n", + "\n", + "# возьмем очень простой словарь\n", + "d_var = {\"apple\": 3, \"tomato\": 6, \"carrot\": 2}\n", + "# вначале поищем яблоки среди ключей словаря\n", + "if \"apple\" in d_var:\n", + " print(\"Нашлись\")\n", + "# а затем посмотрим, нет ли числа 6 среди его значений\n", + "# с помощью метода .values()\n", + "if 6 in d_var.values():\n", + " print(\"Есть\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56928ea3", + "metadata": {}, + "outputs": [], + "source": [ + "# Циклы в питоне\n", + "# цикл for\n", + "\n", + "# поочередно выведем элементы списка\n", + "my_list = [1, 2, 3, 4, 5]\n", + "for item in my_list:\n", + " print(item)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9aa517ff", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим словарь, значениями которого будут списки из двух элементов\n", + "# затем создадим две переменные-контейнера и применим метод .items()\n", + "\n", + "goods_dict: Dict[str, List[Union[int, str]]] = {\n", + " \"apple\": [3, \"kg\"],\n", + " \"tomato\": [6, \"pcs\"],\n", + " \"carrot\": [2, \"kg\"],\n", + "}\n", + "\n", + "for key, value in goods_dict.items():\n", + " print(key, value)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7330d363", + "metadata": {}, + "outputs": [], + "source": [ + "# возьмем только одну переменную и применим метод .values()\n", + "for value in goods_dict.values():\n", + " # значение представляет собой список, выведем его первый\n", + " # элемент с индексом [0]\n", + " print(value[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3eaa725", + "metadata": {}, + "outputs": [], + "source": [ + "# предположим, что у нас есть следующая база данных клиентов\n", + "clients = {\n", + " 1: {\"name\": \"Анна\", \"age\": 24, \"sex\": \"male\", \"revenue\": 12000},\n", + " 2: {\"name\": \"Илья\", \"age\": 18, \"sex\": \"female\", \"revenue\": 8000},\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7473710", + "metadata": {}, + "outputs": [], + "source": [ + "for i, info in clients.items():\n", + " print(f\"clientID: {i}\")\n", + "\n", + " # во втором цикле возьмем информацию об этом клиенте (это тоже словарь)\n", + " for field_name, field_value in info.items():\n", + " # и выведем каждый ключ (название поля) и значение (саму информацию)\n", + " print(f\"{field_name}: {field_value}\")\n", + " # добавим пустую строку после того, как выведем информацию об одном клиенте\n", + " print()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72a3fc05", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим последовательность от 0 до 4\n", + "for i in range(5):\n", + " print(i)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c37e31c9", + "metadata": {}, + "outputs": [], + "source": [ + "# и от 0 до 5 с шагом 2 (то есть будем выводить числа через одно)\n", + "for i in range(0, 6, 2):\n", + " print(i)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6084aab", + "metadata": {}, + "outputs": [], + "source": [ + "# возьмем месяцы года\n", + "months = [\n", + " \"January\",\n", + " \"February\",\n", + " \"March\",\n", + " \"April\",\n", + " \"May\",\n", + " \"June\",\n", + " \"July\",\n", + " \"August\",\n", + " \"September\",\n", + " \"October\",\n", + " \"November\",\n", + " \"December\",\n", + "]\n", + "# и продажи мороженого в тыс. рублей в каждый из месяцев\n", + "sales = [120, 150, 170, 130, 160, 180, 200, 210, 190, 220, 230, 250]\n", + "# задав последовательность через enumerate,\n", + "for i, month in enumerate(months):\n", + " # мы можем вывести каждый из элементов обоих списков в одном цикле\n", + " print(f\"{i}: {month}, {sales[i]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e21a8bd7", + "metadata": {}, + "outputs": [], + "source": [ + "# выведем числа от 1 до 10, но только четные\n", + "for i in range(1, 11):\n", + " # если число четное, выведем его\n", + " if i % 2 == 0:\n", + " print(i)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a782139a", + "metadata": {}, + "outputs": [], + "source": [ + "# Последовательность в обратном порядке\n", + "# Способ 1. Функция reversed()\n", + "\n", + "# создадим список\n", + "my_list = [1, 2, 3, 4, 5]\n", + "\n", + "# выведем элементы списка в обратном порядке с помощью функции reversed()\n", + "for item in reversed(my_list):\n", + " print(item)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f1dc1135", + "metadata": {}, + "outputs": [], + "source": [ + "# или с помощью range()\n", + "for i in reversed(range(5)):\n", + " print(i)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30b122f9", + "metadata": {}, + "outputs": [], + "source": [ + "# Способ 2. Указать −1 в качестве параметра шага\n", + "# чтобы вывести 0, вторым параметром нужно указать -1\n", + "for i in range(4, -1, -1):\n", + " print(i)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c417f0e", + "metadata": {}, + "outputs": [], + "source": [ + "# Способ 3. Функция sorted()\n", + "\n", + "for i in sorted(my_list, reverse=True):\n", + " print(i)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b996cde8", + "metadata": {}, + "outputs": [], + "source": [ + "# Функция enumerate()\n", + "# пусть дан список с днями недели\n", + "days = [\n", + " \"Понедельник\",\n", + " \"Вторник\",\n", + " \"Среда\",\n", + " \"Четверг\",\n", + " \"Пятница\",\n", + " \"Суббота\",\n", + " \"Воскресенье\",\n", + "]\n", + "\n", + "# выведем индекс (i) и сами элементы списка (day)\n", + "# выведем индекс и элементы списка, но начнем с 1\n", + "for i, day in enumerate(days, start=1):\n", + " print(i, day)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6027cbaa", + "metadata": {}, + "outputs": [], + "source": [ + "# Цикл while\n", + "# зададим начальное значение счетчика\n", + "counter = 0\n", + "# пока счетчик меньше трех\n", + "while counter < 3:\n", + " print(f\"Текущее значение счетчика: {counter}\")\n", + " # увеличим значение счетчика на единицу\n", + " counter += 1\n", + " print(f\"Новое значение счетчика: {counter}\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9af01d6", + "metadata": {}, + "outputs": [], + "source": [ + "# Break, continue\n", + "# Оператор break\n", + "\n", + "# создадим константу для выхода из цикла и счетчик\n", + "EXIT_NUMBER = 5\n", + "counter = 0\n", + "# создадим бесконечный цикл\n", + "while True:\n", + " # сделаем условие выхода из цикла\n", + " if counter == EXIT_NUMBER:\n", + " break\n", + " counter += 1\n", + " print(counter)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c8db65a", + "metadata": {}, + "outputs": [], + "source": [ + "# Оператор continue\n", + "\n", + "# выведем числа от 1 до 10, но только четные c помощью оператора continue\n", + "for i in range(1, 11):\n", + " # если число нечетное, пропустим его\n", + " if i % 2 != 0:\n", + " continue\n", + " # если число четное, выведем его\n", + " print(i)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_3_if_and_loops.py b/python/makarov/chapter_3_if_and_loops.py new file mode 100644 index 00000000..82882712 --- /dev/null +++ b/python/makarov/chapter_3_if_and_loops.py @@ -0,0 +1,224 @@ +"""Условия и циклы. + +Продолжение. +""" + +from typing import Union + +# + +# Еще раз про условия с if +# напишем программу, которая разобьет все числа на малые, +# средние и большие + +# запросим число у пользователя и преобразуем в тип int +x_var = int(input()) + +# и наконец классифицируем число +if x_var < 10: + print("Small number") +elif x_var < 100: + print("Medium number") +else: + print("Large number") + +# + +# Несколько условий в одном выражении с операторами and или or + +x_var = int(input()) + +if x_var < 10 or x_var > 100: + print("Small or large number") +else: + print("Medium number") + +# + +# Проверка вхождения элемента в объект с in / not in + +# можно проверить вхождение слова в строку +sentence = "To be, or not to be, that is the question" +word = "question" +if word in sentence: + print(f"Слово {word} найдено.") +# - + +# или отсутствие элемента в списке +my_list = [1, 2, 3, 4, 5] +number = 10 +if number not in my_list: + print(f"Элемент {number} отсутствует в списке.") + +# + +# кроме того, можно проверить вхождение ключа и значения в словарь + +# возьмем очень простой словарь +d_var = {"apple": 3, "tomato": 6, "carrot": 2} +# вначале поищем яблоки среди ключей словаря +if "apple" in d_var: + print("Нашлись") +# а затем посмотрим, нет ли числа 6 среди его значений +# с помощью метода .values() +if 6 in d_var.values(): + print("Есть") + +# + +# Циклы в питоне +# цикл for + +# поочередно выведем элементы списка +my_list = [1, 2, 3, 4, 5] +for item in my_list: + print(item) + +# + +# создадим словарь, значениями которого будут списки из двух элементов +# затем создадим две переменные-контейнера и применим метод .items() + +goods_dict: dict[str, list[Union[int, str]]] = { + "apple": [3, "kg"], + "tomato": [6, "pcs"], + "carrot": [2, "kg"], +} + +for key, value in goods_dict.items(): + print(key, value) +# - + +# возьмем только одну переменную и применим метод .values() +for value in goods_dict.values(): + # значение представляет собой список, выведем его первый + # элемент с индексом [0] + print(value[0]) + +# предположим, что у нас есть следующая база данных клиентов +clients = { + 1: {"name": "Анна", "age": 24, "sex": "male", "revenue": 12000}, + 2: {"name": "Илья", "age": 18, "sex": "female", "revenue": 8000}, +} + +for i, info in clients.items(): + print(f"clientID: {i}") + + # во втором цикле возьмем информацию об этом клиенте (это тоже словарь) + for field_name, field_value in info.items(): + # и выведем каждый ключ (название поля) и значение (саму информацию) + print(f"{field_name}: {field_value}") + # добавим пустую строку после того, как выведем информацию об одном клиенте + print() + +# создадим последовательность от 0 до 4 +for i in range(5): + print(i) + +# и от 0 до 5 с шагом 2 (то есть будем выводить числа через одно) +for i in range(0, 6, 2): + print(i) + +# возьмем месяцы года +months = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +] +# и продажи мороженого в тыс. рублей в каждый из месяцев +sales = [120, 150, 170, 130, 160, 180, 200, 210, 190, 220, 230, 250] +# задав последовательность через enumerate, +for i, month in enumerate(months): + # мы можем вывести каждый из элементов обоих списков в одном цикле + print(f"{i}: {month}, {sales[i]}") + +# выведем числа от 1 до 10, но только четные +for i in range(1, 11): + # если число четное, выведем его + if i % 2 == 0: + print(i) + +# + +# Последовательность в обратном порядке +# Способ 1. Функция reversed() + +# создадим список +my_list = [1, 2, 3, 4, 5] + +# выведем элементы списка в обратном порядке с помощью функции reversed() +for item in reversed(my_list): + print(item) +# - + +# или с помощью range() +for i in reversed(range(5)): + print(i) + +# Способ 2. Указать −1 в качестве параметра шага +# чтобы вывести 0, вторым параметром нужно указать -1 +for i in range(4, -1, -1): + print(i) + +# + +# Способ 3. Функция sorted() + +for i in sorted(my_list, reverse=True): + print(i) + +# + +# Функция enumerate() +# пусть дан список с днями недели +days = [ + "Понедельник", + "Вторник", + "Среда", + "Четверг", + "Пятница", + "Суббота", + "Воскресенье", +] + +# выведем индекс (i) и сами элементы списка (day) +# выведем индекс и элементы списка, но начнем с 1 +for i, day in enumerate(days, start=1): + print(i, day) +# - + +# Цикл while +# зададим начальное значение счетчика +counter = 0 +# пока счетчик меньше трех +while counter < 3: + print(f"Текущее значение счетчика: {counter}") + # увеличим значение счетчика на единицу + counter += 1 + print(f"Новое значение счетчика: {counter}\n") + +# + +# Break, continue +# Оператор break + +# создадим константу для выхода из цикла и счетчик +EXIT_NUMBER = 5 +counter = 0 +# создадим бесконечный цикл +while True: + # сделаем условие выхода из цикла + if counter == EXIT_NUMBER: + break + counter += 1 + print(counter) + +# + +# Оператор continue + +# выведем числа от 1 до 10, но только четные c помощью оператора continue +for i in range(1, 11): + # если число нечетное, пропустим его + if i % 2 != 0: + continue + # если число четное, выведем его + print(i) diff --git a/python/makarov/chapter_4_files.ipynb b/python/makarov/chapter_4_files.ipynb index 12024fbe..b7d330e6 100644 --- a/python/makarov/chapter_4_files.ipynb +++ b/python/makarov/chapter_4_files.ipynb @@ -30,6 +30,7 @@ "outputs": [], "source": [ "# Способ 2. Через модуль files библиотеки google.colab\n", + "# Только для google.colab\n", "\n", "# импортируем модуль os\n", "import os\n", @@ -236,7 +237,7 @@ "# Приведу пример того, что мы хотим получить.\n", "\n", "# файл с примером можно загрузить не с локального компьютера, а из Интернета\n", - "url = \"https://www.dmitrymakarov.ru/wp-content/uploads/2021/11/titanic_example.csv\"\n", + "url = \"https://www.dmitrymakarov.ru/wp-content/\" + \"uploads/2021/11/titanic_example.csv\"\n", "\n", "# просто поместим его url в функцию read_csv()\n", "example = pd.read_csv(url)\n", diff --git a/python/makarov/chapter_4_files.py b/python/makarov/chapter_4_files.py new file mode 100644 index 00000000..0b4b5d47 --- /dev/null +++ b/python/makarov/chapter_4_files.py @@ -0,0 +1,166 @@ +"""Работа с файлами в Google Colab.""" + +# + +# Подгрузка файлов с локального компьютера на сервер Google. +# Способ 1. Вручную через вкладку 'Файлы' +# см. материалы урока на сайте + +# + +# Способ 2. Через модуль files библиотеки google.colab +# Только для google.colab + +# импортируем модуль os +import os + +# импортируем библиотеку +import pandas as pd + +# из библиотеки google.colab импортируем класс files +from google.colab import files + +# создаем объект этого класса, применяем метод .upload() +uploaded = files.upload() +# Нам будет предложено выбрать файл на жестком диске. + +# посмотрим на содержимое словаря uploaded +uploaded + +# + +# Чтение файлов +# После загрузки оба файла (train.csv и test.csv) +# оказываются в сессионном хранилище в папке +# под названием /content/. + +# Просмотр содержимого в папке /content/ +# Модуль os и метод .walk() + + +# выводим пути к папкам (dirpath) и наименования файлов +# (filenames) и после этого +for dirpath, _, filenames in os.walk("/content/"): + + # во вложенном цикле проходимся по названиям файлов + for filename in filenames: + + # и соединяем путь до папок и входящие в эти папки файлы + # с помощью метода path.join() + print(os.path.join(dirpath, filename)) + +# + +# Команда !ls + +# посмотрим на содержимое папки content +# !ls + +# заглянем внутрь sample_data +# !ls /content/sample_data/ + +# + +# Чтение из переменной uploaded + + +# посмотрим на тип значений словаря uploaded +type(uploaded["test.csv"]) + +# bytes + +# обратимся к ключу словаря uploaded и применим метод .decode() +uploaded_str = uploaded["test.csv"].decode() + +# на выходе получаем обычную строку +print(type(uploaded_str)) + +# + +# Если разбить строку методом .split() по символам \r +# (возврат к началу строки) и \n (новая строка), +# то на выходе мы получим список. + +uploaded_list = uploaded_str.split("\r\n") +type(uploaded_list) # list +# - + +# пройдемся по этому списку, не забыв создать индекс с +# помощью функции enumerate() +for i, line in enumerate(uploaded_list): + + # начнем выводить записи + print(line) + + # когда дойдем до четвертой строки + if i == 3: + + # прервемся + break + +# + +# Использование функции open() и конструкции with open() +# Функция open() возвращает объект, который используется +# для чтения и изменения файла. Откроем файл train.csv. + + +# передадим функции open() адрес файла +with open("/content/train.csv", encoding="utf-8") as f: + content = f.read() + +# + +# Для наших целей метод .read() не очень удобен. +# Будет лучше пройтись по файлу в цикле for. + +# снова откроем файл +with open("/content/train.csv", encoding="utf-8") as f1: + for i, line in enumerate(f1): + print(line.strip()) + if i == 3: + break + +# + +# Еще один способ — использовать конструкцию with open(). +# В этом случае специально закрывать файл не нужно. + +# скажем Питону: "открой файл и назови его f3" +with open("/content/test.csv", encoding="utf-8") as f3: + + # "пройдись по строкам без служебных символов" + for i, line in enumerate(f3): + print(line.strip()) + + # и "прервись на четвертой строке" + if i == 3: + break + +# + +# Чтение через библиотеку Pandas + + +# применим функцию read_csv() и посмотрим на первые три записи файла train.csv +train = pd.read_csv("/content/train.csv") +train.head(3) + +# + +# Сохранение результата в новом файле на сервере +# Теперь, когда прогноз готов, мы можем сформировать новый файл, +# назовем его result.csv, в котором будет содержаться id +# пассажира и результат, погиб или нет. +# Приведу пример того, что мы хотим получить. + +# файл с примером можно загрузить не с локального компьютера, а из Интернета +url = "https://www.dmitrymakarov.ru/wp-content/" + "uploads/2021/11/titanic_example.csv" + +# просто поместим его url в функцию read_csv() +example = pd.read_csv(url) +example.head(3) + +# + +# создадим новый файл result.csv с помощью функции to_csv(), +# удалив при этом индекс +# result.to_csv("result.csv", index=False) + +# файл будет сохранен в 'Сессионном хранилище' и, +# если все пройдет успешно, выведем следующий текст: +# print("Файл успешно сохранился в сессионное хранилище!") +# - + +# Скачивание обратно на жесткий диск +# После этого мы можем скачать файл на жесткий диск. +# применим метод .download() объекта files +files.download("/content/result.csv") diff --git a/python/makarov/chapter_5_datetime.ipynb b/python/makarov/chapter_5_datetime.ipynb new file mode 100644 index 00000000..e489af35 --- /dev/null +++ b/python/makarov/chapter_5_datetime.ipynb @@ -0,0 +1,889 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "3db8834e", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Дата и время в Питоне.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d73952d2", + "metadata": {}, + "outputs": [], + "source": [ + "# Модуль datetime\n", + "# В базовом функционале Питона нет отдельного типа данных,\n", + "# отвечающего за дату и время. Необходимо импортировать модуль,\n", + "# который называется datetime.\n", + "\n", + "# для этого вначале импортируем соответствующий класс\n", + "from datetime import datetime, timedelta\n", + "\n", + "# Эту же работу мы можем поручить библиотеке Pandas через функцию\n", + "# read_csv() и параметр parse_dates.\n", + "import pandas as pd\n", + "import pytz" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7395ee30", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-12-20 15:30:12.255005\n" + ] + } + ], + "source": [ + "# чтобы получить доступ к функции now(), сначала обратимся к модулю,\n", + "# потом к классу\n", + "print(datetime.now())" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "81c8fc1b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-12-20 15:30:14.832128\n" + ] + } + ], + "source": [ + "# Как вы видите, это не очень удобно. Можно импортировать только класс\n", + "# datetime и обращаться непосредственно к нему.\n", + "\n", + "\n", + "print(datetime.now())" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "02e1420b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-12-20 15:30:17.077987\n" + ] + } + ], + "source": [ + "# Объект datetime и функция now()\n", + "# поместим созданный с помощью функции now() объект datetime\n", + "# в переменную cur_dt\n", + "cur_dt = datetime.now()\n", + "print(cur_dt)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8c24047a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025 12 20 15 30 17 77987\n" + ] + } + ], + "source": [ + "# с помощью соответствующих атрибутов выведем каждый из\n", + "# компонентов объекта\n", + "\n", + "print(\n", + " cur_dt.year,\n", + " cur_dt.month,\n", + " cur_dt.day,\n", + " cur_dt.hour,\n", + " cur_dt.minute,\n", + " cur_dt.second,\n", + " cur_dt.microsecond,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "86fb7190", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5 6\n" + ] + } + ], + "source": [ + "# также можно посмотреть на день недели\n", + "# метод .weekday() начинает индекс недели с нуля,\n", + "# .isoweekday() - с единицы\n", + "print(cur_dt.weekday(), cur_dt.isoweekday())" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1bd0d175", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "None\n" + ] + } + ], + "source": [ + "# посмотрим на часовой пояс с помощью атрибута tzinfo\n", + "print(cur_dt.tzinfo)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "5032cf5c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-12-20 15:30:28.100029+03:00\n" + ] + } + ], + "source": [ + "# Для того чтобы добавить такую информацию и вывести,\n", + "# например, другой часовой пояс, нам нужно воспользоваться\n", + "# модулем pytz.\n", + "\n", + "\n", + "# выведем текущее время в Москве\n", + "dt_moscow = datetime.now(pytz.timezone(\"Europe/Moscow\"))\n", + "print(dt_moscow)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "5fe2bf48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Europe/Moscow\n" + ] + } + ], + "source": [ + "# Посмотрим, не появился ли часовой пояс.\n", + "\n", + "print(dt_moscow.tzinfo)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "a555eafe", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1766233832.235268\n" + ] + } + ], + "source": [ + "# Timestamp\n", + "# компьютеры используют так называемое время Unix, которое\n", + "# отсчитывается в секундах c первого января 1970 года.\n", + "# Для отображения даты и времени в таком формате в\n", + "# Питоне есть объект timestamp (по-английски — «временная отметка»)\n", + "\n", + "# получим timestamp текущего времени с помощью метода .timestamp()\n", + "timestamp = datetime.now().timestamp()\n", + "print(timestamp)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "000a3bad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-12-20 15:30:32.235268\n" + ] + } + ], + "source": [ + "# Не составляет труда вернуть timestamp обратно в привычный формат.\n", + "# для этого воспользуемся методом .fromtimestamp()\n", + "print(datetime.fromtimestamp(timestamp))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "99c3ebcf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1991-02-20 00:00:00\n" + ] + } + ], + "source": [ + "# Создание объекта datetime вручную\n", + "# Дату и время не обязательно получать из функции now().\n", + "# Мы вполне можем передать объекту datetime наши собственные\n", + "# параметры, например, день рождения Питона.\n", + "\n", + "# передадим объекту datetime 20 февраля 1991 года\n", + "hb = datetime(1991, 2, 20)\n", + "print(hb)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "a1608cfa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1991\n" + ] + } + ], + "source": [ + "# извлечем год с помощью атрибута year\n", + "print(hb.year)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "6415b6a5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "666997200.0\n" + ] + } + ], + "source": [ + "# создадим timestamp\n", + "print(datetime.timestamp(hb))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "373acef2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "str" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Преобразование строки в datetime и наоборот\n", + "# дана строка с датой 2 декабря 2007 года и временем\n", + "# 12 часов 30 минут и 45 секунд\n", + "str_to_dt = \"2007-12-02 12:30:45\"\n", + "type(str_to_dt)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "d18fd75b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2007-12-02 12:30:45\n", + "\n" + ] + } + ], + "source": [ + "# Преобразуем эту строку в объект datetime с помощью метода .strptime().\n", + "\n", + "res_dt = datetime.strptime(str_to_dt, \"%Y-%m-%d %H:%M:%S\")\n", + "\n", + "print(res_dt)\n", + "print(type(res_dt))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "6dabdf07", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "datetime.datetime" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Datetime в строку через .strftime()\n", + "# вначале создадим объект datetime и передадим ему текущую дату\n", + "dt_to_str = datetime(2025, 12, 5)\n", + "type(dt_to_str)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "63cc4f89", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Friday, December 05, 2025\n", + "\n" + ] + } + ], + "source": [ + "# преобразуем объект в строку в формате \"день недели, месяц число, год\"\n", + "res_str = datetime.strftime(dt_to_str, \"%A, %B %d, %Y\")\n", + "\n", + "print(res_str)\n", + "print(type(res_str))" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "7e78d33e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Friday, December 05, 2025'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Метод .strftime() можно применять непосредственно к объекту datetime.\n", + "\n", + "dt_to_str.strftime(\"%A, %B %d, %Y\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "cd85dff9", + "metadata": {}, + "outputs": [], + "source": [ + "# Сравнение и арифметика дат\n", + "# Сравнение дат\n", + "\n", + "# Даты можно сравнивать между собой. Для этого\n", + "# используются стандартные операторы сравнения >, <, >=, <=, ==, !=.\n", + "\n", + "date1 = datetime(1905, 6, 30) # \"К электродинамике движущихся тел\"\n", + "date2 = datetime(1916, 5, 11) # Общая теория относительности" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "f0baf123", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n", + "False\n" + ] + } + ], + "source": [ + "# Вторая дата должна быть «больше», потому что она более поздняя.\n", + "# Обратное будет признано ложным.\n", + "\n", + "print(date1 < date2)\n", + "print(date1 > date2)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "2acc0def", + "metadata": {}, + "outputs": [], + "source": [ + "# Календарный и алфавитный порядок дат\n", + "# Интересно, что если даты записаны в виде строки в формате\n", + "# ГГГГ.ММ.ДД, то в Питоне мы можем их сравнивать, как если\n", + "# бы мы сравнивали объекты datetime. Другими словами,\n", + "# календарный и алфавитный порядок дат совпадают" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "eb03d9ae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3968 days, 0:00:00\n" + ] + } + ], + "source": [ + "# Промежуток времени и класс timedelta\n", + "\n", + "# Если из большей даты вычесть меньшую, то мы получим\n", + "# временной промежуток между датами.\n", + "\n", + "delta = date2 - date1\n", + "print(delta)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "c61b36dc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "datetime.timedelta" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# При этом результат будет храниться в специальном объекте timedelta.\n", + "\n", + "type(delta)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "f91c0003", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3968\n" + ] + } + ], + "source": [ + "# Атрибут days позволяет посмотреть только дни.\n", + "\n", + "print(delta.days)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "93e3f731", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "datetime.timedelta(days=1)" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Объект timedelta также можно создать вручную.\n", + "\n", + "\n", + "# а затем создадим объект timedelta продолжительностью 1 день\n", + "timedelta(days=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b7cc3438", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "datetime.datetime(2070, 1, 1, 0, 0)" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Арифметика дат\n", + "# Объединив объекты datetime и timedelta, мы можем «путешествовать во времени».\n", + "# допустим сейчас 1 января 2070 года\n", + "future = datetime(2070, 1, 1)\n", + "future" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "f3417521", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "datetime.datetime(1900, 2, 12, 0, 0)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# И мы хотим отправиться в 1 января 1900 года, т.е. на 170 лет назад.\n", + "\n", + "# сначала просто умножим 365 дней на 170\n", + "time_travel = timedelta(days=365) * 170\n", + "\n", + "# а потом переместимся из будущего в прошлое\n", + "past = future - time_travel\n", + "\n", + "# к сожалению, мы немного \"не долетим\", потому что не\n", + "# учли високосные годы, в которых 366 дней\n", + "past" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "ae1403a2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "datetime.datetime(1900, 1, 1, 0, 0)" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Теперь снова совершим путешествие во времени,\n", + "# но на этот раз укажем правильное количество дней.\n", + "\n", + "time_travel = timedelta(days=62092)\n", + "\n", + "past = future - time_travel\n", + "past" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "31c675e4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Jan 01, 2021\n", + "Jan 02, 2021\n", + "Jan 03, 2021\n", + "Jan 04, 2021\n", + "Jan 05, 2021\n", + "Jan 06, 2021\n", + "Jan 07, 2021\n", + "Jan 08, 2021\n", + "Jan 09, 2021\n", + "Jan 10, 2021\n" + ] + } + ], + "source": [ + "# Объект timedelta можно также прибавлять к объекту datetime.\n", + "# Например, нам может быть нужно создать перечень дат, пусть это будут\n", + "# новогодние празники в 2021 году.\n", + "# Для этого удобно использовать цикл while.\n", + "\n", + "cur_date = datetime(2021, 1, 1) # эту дату мы будем выводить\n", + "end_date = datetime(2021, 1, 10) # это граница (условие в цикле while)\n", + "\n", + "# пока верно условие\n", + "while cur_date <= end_date:\n", + "\n", + " # выведем cur_date в формате \"месяц число, год\"\n", + " print(cur_date.strftime(\"%b %d, %Y\"))\n", + "\n", + " # прибавим к выводимой дате один день\n", + " cur_date += timedelta(days=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "093299ea", + "metadata": {}, + "outputs": [], + "source": [ + "# Дата и обработка ошибок\n", + "# Конструкция try / except и оператор pass\n", + "\n", + "# Часто мы не уверены, что наш код отработает без ошибок.\n", + "# Например, в данных может содержаться неточность,\n", + "# о которой мы ничего не знаем.\n", + "# При этом нам не хотелось бы, чтобы исполнение кода остановилось.\n", + "\n", + "# Для этого в Питоне есть конструкция try/except." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "a49ed574", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "40" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# пусть дан список чисел в строковом формате,\n", + "# и мы хотим посчитать их сумму\n", + "# предположим, буква \"а\" попала в список случайно\n", + "numbers = [\"5\", \"10\", \"a\", \"15\", \"10\"]\n", + "\n", + "# объявим переменную суммы\n", + "total = 0\n", + "\n", + "# пройдемся по числам\n", + "for number in numbers:\n", + "\n", + " # попробуем прибавить число к переменной total\n", + " try:\n", + " total += int(number)\n", + "\n", + " # если же этого сделать не удастся\n", + " except ValueError:\n", + " # перейдем к следующему числу\n", + " pass\n", + "\n", + "# выведем сумму\n", + "total" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "0883964e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Элемент 'a' обработать не удалось\n" + ] + }, + { + "data": { + "text/plain": [ + "40" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Оператор pass просто говорит алгоритму продолжить работу.\n", + "# Вместо него можно вывести предупреждение.\n", + "\n", + "total = 0\n", + "\n", + "for number in numbers:\n", + " try:\n", + " total += int(number)\n", + " except ValueError:\n", + " print(f\"Элемент '{number}' обработать не удалось\")\n", + "\n", + "total" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f4f62fb", + "metadata": {}, + "outputs": [], + "source": [ + "# Обработка нескольких форматов дат\n", + "temp: pd.DataFrame = pd.read_csv(\"temperature.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a6c5f08", + "metadata": {}, + "outputs": [], + "source": [ + "formats_var: list[str] = [\"%Y-%m-%d\", \"%Y-%m-%-d\", \"%Y-%m\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f29d602f", + "metadata": {}, + "outputs": [], + "source": [ + "counter: int = 0\n", + "\n", + "for d_var in temp.Date:\n", + " for format_var in formats_var:\n", + " try:\n", + " print(datetime.strptime(d_var, format_var))\n", + " counter += 1\n", + " except ValueError:\n", + " pass\n", + "\n", + "print(\"Не отобразилось записей:\", len(temp) - counter)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2a219f1", + "metadata": {}, + "outputs": [], + "source": [ + "# Эту же работу мы можем поручить библиотеке Pandas\n", + "# через функцию read_csv() и параметр parse_dates\n", + "\n", + "temp_parsed: pd.DataFrame = pd.read_csv(\n", + " \"temperature.csv\", index_col=\"Date\", parse_dates=True\n", + ")\n", + "temp_parsed" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_5_datetime.py b/python/makarov/chapter_5_datetime.py new file mode 100644 index 00000000..fa616c1f --- /dev/null +++ b/python/makarov/chapter_5_datetime.py @@ -0,0 +1,312 @@ +"""Дата и время в Питоне.""" + +# + +# Модуль datetime +# В базовом функционале Питона нет отдельного типа данных, +# отвечающего за дату и время. Необходимо импортировать модуль, +# который называется datetime. + +# для этого вначале импортируем соответствующий класс +from datetime import datetime, timedelta + +# Эту же работу мы можем поручить библиотеке Pandas через функцию +# read_csv() и параметр parse_dates. +import pandas as pd +import pytz + +# - + +# чтобы получить доступ к функции now(), сначала обратимся к модулю, +# потом к классу +print(datetime.now()) + +# + +# Как вы видите, это не очень удобно. Можно импортировать только класс +# datetime и обращаться непосредственно к нему. + + +print(datetime.now()) +# - + +# Объект datetime и функция now() +# поместим созданный с помощью функции now() объект datetime +# в переменную cur_dt +cur_dt = datetime.now() +print(cur_dt) + +# + +# с помощью соответствующих атрибутов выведем каждый из +# компонентов объекта + +print( + cur_dt.year, + cur_dt.month, + cur_dt.day, + cur_dt.hour, + cur_dt.minute, + cur_dt.second, + cur_dt.microsecond, +) +# - + +# также можно посмотреть на день недели +# метод .weekday() начинает индекс недели с нуля, +# .isoweekday() - с единицы +print(cur_dt.weekday(), cur_dt.isoweekday()) + +# посмотрим на часовой пояс с помощью атрибута tzinfo +print(cur_dt.tzinfo) + +# + +# Для того чтобы добавить такую информацию и вывести, +# например, другой часовой пояс, нам нужно воспользоваться +# модулем pytz. + + +# выведем текущее время в Москве +dt_moscow = datetime.now(pytz.timezone("Europe/Moscow")) +print(dt_moscow) + +# + +# Посмотрим, не появился ли часовой пояс. + +print(dt_moscow.tzinfo) + +# + +# Timestamp +# компьютеры используют так называемое время Unix, которое +# отсчитывается в секундах c первого января 1970 года. +# Для отображения даты и времени в таком формате в +# Питоне есть объект timestamp (по-английски — «временная отметка») + +# получим timestamp текущего времени с помощью метода .timestamp() +timestamp = datetime.now().timestamp() +print(timestamp) +# - + +# Не составляет труда вернуть timestamp обратно в привычный формат. +# для этого воспользуемся методом .fromtimestamp() +print(datetime.fromtimestamp(timestamp)) + +# + +# Создание объекта datetime вручную +# Дату и время не обязательно получать из функции now(). +# Мы вполне можем передать объекту datetime наши собственные +# параметры, например, день рождения Питона. + +# передадим объекту datetime 20 февраля 1991 года +hb = datetime(1991, 2, 20) +print(hb) +# - + +# извлечем год с помощью атрибута year +print(hb.year) + +# создадим timestamp +print(datetime.timestamp(hb)) + +# Преобразование строки в datetime и наоборот +# дана строка с датой 2 декабря 2007 года и временем +# 12 часов 30 минут и 45 секунд +str_to_dt = "2007-12-02 12:30:45" +type(str_to_dt) + +# + +# Преобразуем эту строку в объект datetime с помощью метода .strptime(). + +res_dt = datetime.strptime(str_to_dt, "%Y-%m-%d %H:%M:%S") + +print(res_dt) +print(type(res_dt)) +# - + +# Datetime в строку через .strftime() +# вначале создадим объект datetime и передадим ему текущую дату +dt_to_str = datetime(2025, 12, 5) +type(dt_to_str) + +# + +# преобразуем объект в строку в формате "день недели, месяц число, год" +res_str = datetime.strftime(dt_to_str, "%A, %B %d, %Y") + +print(res_str) +print(type(res_str)) + +# + +# Метод .strftime() можно применять непосредственно к объекту datetime. + +dt_to_str.strftime("%A, %B %d, %Y") + +# + +# Сравнение и арифметика дат +# Сравнение дат + +# Даты можно сравнивать между собой. Для этого +# используются стандартные операторы сравнения >, <, >=, <=, ==, !=. + +date1 = datetime(1905, 6, 30) # "К электродинамике движущихся тел" +date2 = datetime(1916, 5, 11) # Общая теория относительности + +# + +# Вторая дата должна быть «больше», потому что она более поздняя. +# Обратное будет признано ложным. + +print(date1 < date2) +print(date1 > date2) + +# + +# Календарный и алфавитный порядок дат +# Интересно, что если даты записаны в виде строки в формате +# ГГГГ.ММ.ДД, то в Питоне мы можем их сравнивать, как если +# бы мы сравнивали объекты datetime. Другими словами, +# календарный и алфавитный порядок дат совпадают + +# + +# Промежуток времени и класс timedelta + +# Если из большей даты вычесть меньшую, то мы получим +# временной промежуток между датами. + +delta = date2 - date1 +print(delta) + +# + +# При этом результат будет храниться в специальном объекте timedelta. + +type(delta) + +# + +# Атрибут days позволяет посмотреть только дни. + +print(delta.days) + +# + +# Объект timedelta также можно создать вручную. + + +# а затем создадим объект timedelta продолжительностью 1 день +timedelta(days=1) +# - + +# Арифметика дат +# Объединив объекты datetime и timedelta, мы можем «путешествовать во времени». +# допустим сейчас 1 января 2070 года +future = datetime(2070, 1, 1) +future + +# + +# И мы хотим отправиться в 1 января 1900 года, т.е. на 170 лет назад. + +# сначала просто умножим 365 дней на 170 +time_travel = timedelta(days=365) * 170 + +# а потом переместимся из будущего в прошлое +past = future - time_travel + +# к сожалению, мы немного "не долетим", потому что не +# учли високосные годы, в которых 366 дней +past + +# + +# Теперь снова совершим путешествие во времени, +# но на этот раз укажем правильное количество дней. + +time_travel = timedelta(days=62092) + +past = future - time_travel +past + +# + +# Объект timedelta можно также прибавлять к объекту datetime. +# Например, нам может быть нужно создать перечень дат, пусть это будут +# новогодние празники в 2021 году. +# Для этого удобно использовать цикл while. + +cur_date = datetime(2021, 1, 1) # эту дату мы будем выводить +end_date = datetime(2021, 1, 10) # это граница (условие в цикле while) + +# пока верно условие +while cur_date <= end_date: + + # выведем cur_date в формате "месяц число, год" + print(cur_date.strftime("%b %d, %Y")) + + # прибавим к выводимой дате один день + cur_date += timedelta(days=1) + +# + +# Дата и обработка ошибок +# Конструкция try / except и оператор pass + +# Часто мы не уверены, что наш код отработает без ошибок. +# Например, в данных может содержаться неточность, +# о которой мы ничего не знаем. +# При этом нам не хотелось бы, чтобы исполнение кода остановилось. + +# Для этого в Питоне есть конструкция try/except. + +# + +# пусть дан список чисел в строковом формате, +# и мы хотим посчитать их сумму +# предположим, буква "а" попала в список случайно +numbers = ["5", "10", "a", "15", "10"] + +# объявим переменную суммы +total = 0 + +# пройдемся по числам +for number in numbers: + + # попробуем прибавить число к переменной total + try: + total += int(number) + + # если же этого сделать не удастся + except ValueError: + # перейдем к следующему числу + pass + +# выведем сумму +total + +# + +# Оператор pass просто говорит алгоритму продолжить работу. +# Вместо него можно вывести предупреждение. + +total = 0 + +for number in numbers: + try: + total += int(number) + except ValueError: + print(f"Элемент '{number}' обработать не удалось") + +total +# - + +# Обработка нескольких форматов дат +temp: pd.DataFrame = pd.read_csv("temperature.csv") + +formats_var: list[str] = ["%Y-%m-%d", "%Y-%m-%-d", "%Y-%m"] + +# + +counter: int = 0 + +for d_var in temp.Date: + for format_var in formats_var: + try: + print(datetime.strptime(d_var, format_var)) + counter += 1 + except ValueError: + pass + +print("Не отобразилось записей:", len(temp) - counter) + +# + +# Эту же работу мы можем поручить библиотеке Pandas +# через функцию read_csv() и параметр parse_dates + +temp_parsed: pd.DataFrame = pd.read_csv( + "temperature.csv", index_col="Date", parse_dates=True +) +temp_parsed diff --git a/python/makarov/chapter_6_functions.ipynb b/python/makarov/chapter_6_functions.ipynb new file mode 100644 index 00000000..c05cf47b --- /dev/null +++ b/python/makarov/chapter_6_functions.ipynb @@ -0,0 +1,958 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "19a65ca2", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Функции.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e87cb11", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "id": "e25888d7", + "metadata": {}, + "source": [ + "## Встроенные функции\n", + "В Python есть множество встроенных функций:\n", + "1. Стандартный функционал (built-in functions)\n", + "2. Дополнительные библиотеки (library functions)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e25888d7_2", + "metadata": {}, + "outputs": [], + "source": [ + "np.random.seed(42)\n", + "height = list(np.round(np.random.normal(180, 10, 1000)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3fdcd380", + "metadata": {}, + "outputs": [], + "source": [ + "plt.hist(height, bins=10, edgecolor=\"black\")\n", + "plt.title(\"Распределение роста\")\n", + "plt.xlabel(\"Рост (см)\")\n", + "plt.ylabel(\"Количество\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "1c116cf7", + "metadata": {}, + "source": [ + "## Параметры и аргументы функции\n", + "- **Параметр** — это то, что запрашивает функция при вызове (например, `bins`)\n", + "- **Аргумент** — значение этого параметра (например, `10`)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2ceefd6", + "metadata": {}, + "outputs": [], + "source": [ + "plt.hist(bins=10, x=height)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c339dfd0", + "metadata": {}, + "outputs": [], + "source": [ + "plt.hist(height)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6a4e3ca", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Первая строка\")\n", + "print()\n", + "print(\"Третья строка\")" + ] + }, + { + "cell_type": "markdown", + "id": "9559fe4b", + "metadata": {}, + "source": [ + "## Функции и методы\n", + "Методы — это функции, которые можно применить только к конкретному объекту." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9559fe4b_2", + "metadata": {}, + "outputs": [], + "source": [ + "some_string = \"machine learning\"\n", + "some_string.title()" + ] + }, + { + "cell_type": "markdown", + "id": "abda7d7f", + "metadata": {}, + "source": [ + "## Собственные функции в Python\n", + "### Объявление и вызов функции" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "abda7d7f_2", + "metadata": {}, + "outputs": [], + "source": [ + "def double(value: int) -> int:\n", + " \"\"\"Удваивает переданное значение.\"\"\"\n", + " return value * 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "193a6e20", + "metadata": {}, + "outputs": [], + "source": [ + "double(5)" + ] + }, + { + "cell_type": "markdown", + "id": "f33a6131", + "metadata": {}, + "source": [ + "## Пустое тело функции\n", + "Тело функции не может быть пустым. Нужно как минимум указать `return` или `pass`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d0cb7f3", + "metadata": {}, + "outputs": [], + "source": [ + "def only_pass() -> None:\n", + " \"\"\"Функция с pass.\"\"\"\n", + " # pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8b49856", + "metadata": {}, + "outputs": [], + "source": [ + "only_pass()" + ] + }, + { + "cell_type": "markdown", + "id": "38984d9a", + "metadata": {}, + "source": [ + "## Функция print() вместо return\n", + "Результат работы функции можно вывести с помощью `print()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38984d9a_2", + "metadata": {}, + "outputs": [], + "source": [ + "def double_print(value: int) -> None:\n", + " \"\"\"Удваивает значение и выводит результат.\"\"\"\n", + " res = value * 2\n", + " print(res)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "288e54f2", + "metadata": {}, + "outputs": [], + "source": [ + "double_print(5)" + ] + }, + { + "cell_type": "markdown", + "id": "dd0e275f", + "metadata": {}, + "source": [ + "### Различие между return и print\n", + "- **return** возвращает значение функции и прерывает её работу\n", + "- **print()** просто выводит значение и не влияет на дальнейшее исполнение кода" + ] + }, + { + "cell_type": "markdown", + "id": "812863d0", + "metadata": {}, + "source": [ + "## Параметры собственных функций\n", + "Параметры могут быть позиционными и именованными." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "812863d0_2", + "metadata": {}, + "outputs": [], + "source": [ + "def calc_sum(first_num: int, second_num: int) -> int:\n", + " \"\"\"Возвращает сумму двух чисел.\"\"\"\n", + " return first_num + second_num" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8ade467", + "metadata": {}, + "outputs": [], + "source": [ + "calc_sum(1, second_num=2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8f69af0", + "metadata": {}, + "outputs": [], + "source": [ + "def calc_sum_default(first_num: int = 1, second_num: int = 2) -> int:\n", + " \"\"\"Возвращает сумму двух чисел с параметрами по умолчанию.\"\"\"\n", + " return first_num + second_num\n", + "\n", + "\n", + "calc_sum_default()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81fa7982", + "metadata": {}, + "outputs": [], + "source": [ + "def greet() -> None:\n", + " \"\"\"Выводит приветствие.\"\"\"\n", + " print(\"Hello, World!\")\n", + "\n", + "\n", + "greet()" + ] + }, + { + "cell_type": "markdown", + "id": "6c3af71f", + "metadata": {}, + "source": [ + "## Аннотация функции\n", + "Аннотация позволяет явно прописать тип данных параметров и возвращаемых значений." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c3af71f_2", + "metadata": {}, + "outputs": [], + "source": [ + "def convert_to_int(value: float = 3.5) -> int:\n", + " \"\"\"Преобразует float в int.\"\"\"\n", + " return int(value)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e2ddef1", + "metadata": {}, + "outputs": [], + "source": [ + "convert_to_int.__annotations__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "316ac177", + "metadata": {}, + "outputs": [], + "source": [ + "convert_to_int()" + ] + }, + { + "cell_type": "markdown", + "id": "b5737a68", + "metadata": {}, + "source": [ + "## Дополнительные возможности функций" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5737a68_2", + "metadata": {}, + "outputs": [], + "source": [ + "result_arithmetic = calc_sum(1, 2) * 2\n", + "print(result_arithmetic)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14b516ea", + "metadata": {}, + "outputs": [], + "source": [ + "result_logic = calc_sum(1, 2) > 2\n", + "print(result_logic)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d30a893d", + "metadata": {}, + "outputs": [], + "source": [ + "def first_letter() -> str:\n", + " \"\"\"Возвращает строку 'Python'.\"\"\"\n", + " return \"Python\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c23e953", + "metadata": {}, + "outputs": [], + "source": [ + "first_char = first_letter()[0]\n", + "print(first_char)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff3a5ff9", + "metadata": {}, + "outputs": [], + "source": [ + "def square_from_input() -> int:\n", + " \"\"\"Запрашивает число и возвращает его квадрат.\"\"\"\n", + " user_inp = int(input(\"Введите число: \"))\n", + " result_local = user_inp**2\n", + " return result_local" + ] + }, + { + "cell_type": "markdown", + "id": "26dee289", + "metadata": {}, + "source": [ + "## Результат вызова функции\n", + "Функция может возвращать список, кортеж или словарь." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26dee289_2", + "metadata": {}, + "outputs": [], + "source": [ + "def create_list(count: int) -> list[int]:\n", + " \"\"\"Создает список чисел от 0 до count-1.\"\"\"\n", + " numbers = []\n", + " for index in range(count):\n", + " numbers.append(index)\n", + " return numbers\n", + "\n", + "\n", + "create_list(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3b1e16b", + "metadata": {}, + "outputs": [], + "source": [ + "def get_tuple_values() -> tuple[str, int]:\n", + " \"\"\"Возвращает кортеж из строки и числа.\"\"\"\n", + " string_val = \"Python\"\n", + " number_val = 42\n", + " return string_val, number_val" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8bd135c4", + "metadata": {}, + "outputs": [], + "source": [ + "str_result, num_result = get_tuple_values()\n", + "print(str_result, num_result)\n", + "print(type(str_result), type(num_result))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf16d7d3", + "metadata": {}, + "outputs": [], + "source": [ + "tuple_result = get_tuple_values()\n", + "print(tuple_result)\n", + "print(type(tuple_result))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40fc9bb6", + "metadata": {}, + "outputs": [], + "source": [ + "def is_even_numbers(number: int) -> bool:\n", + " \"\"\"Проверяет, четное ли число.\"\"\"\n", + " return number % 2 == 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eacb572a", + "metadata": {}, + "outputs": [], + "source": [ + "is_even_numbers(4)" + ] + }, + { + "cell_type": "markdown", + "id": "d30f51f6", + "metadata": {}, + "source": [ + "## Использование библиотек\n", + "Внутри функций можно использовать дополнительные библиотеки Python." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "306fec34", + "metadata": {}, + "outputs": [], + "source": [ + "def mean_f(data: float | list[float] | np.ndarray) -> float:\n", + " \"\"\"Рассчитает среднее арифметическое и прибавит единицу.\"\"\"\n", + " return float(np.mean(data)) + 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "172974ce", + "metadata": {}, + "outputs": [], + "source": [ + "data_values = [1, 2, 3]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d16cff5f", + "metadata": {}, + "outputs": [], + "source": [ + "mean_f([float(x) for x in data_values])" + ] + }, + { + "cell_type": "markdown", + "id": "24766575", + "metadata": {}, + "source": [ + "## Глобальные и локальные переменные\n", + "Переменные существуют либо только внутри функции (локальные), либо во всей программе (глобальные)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24766575_2", + "metadata": {}, + "outputs": [], + "source": [ + "global_name = \"Петр\"\n", + "\n", + "\n", + "def show_name() -> None:\n", + " \"\"\"Выводит глобальное имя.\"\"\"\n", + " print(global_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50aebd6f", + "metadata": {}, + "outputs": [], + "source": [ + "show_name()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29e8f6a1", + "metadata": {}, + "outputs": [], + "source": [ + "def show_local_name() -> None:\n", + " \"\"\"Выводит локальное имя.\"\"\"\n", + " local_name_var = \"Алена\"\n", + " print(local_name_var)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64e91788", + "metadata": {}, + "outputs": [], + "source": [ + "show_local_name()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8e81881", + "metadata": {}, + "outputs": [], + "source": [ + "global_local_name: str\n", + "\n", + "\n", + "def make_global() -> str: # возвращаем str\n", + " \"\"\"Создает имя.\"\"\"\n", + " return \"Алена\"\n", + "\n", + "\n", + "global_local_name = make_global() # присваиваем\n", + "print(global_local_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e6b8f8a", + "metadata": {}, + "outputs": [], + "source": [ + "make_global()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "165b0f07", + "metadata": {}, + "outputs": [], + "source": [ + "print(global_local_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e81c26b", + "metadata": {}, + "outputs": [], + "source": [ + "global_number = 5\n", + "\n", + "\n", + "def print_number() -> None:\n", + " \"\"\"Выводит локальное число.\"\"\"\n", + " local_number = 10\n", + " print(\"Local number:\", local_number)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a516c7b1", + "metadata": {}, + "outputs": [], + "source": [ + "print_number()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a8cbb93", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Global number:\", global_number)" + ] + }, + { + "cell_type": "markdown", + "id": "a0681a5c", + "metadata": {}, + "source": [ + "## Анонимные или lambda-функции\n", + "Функции можно создавать с помощью слова lambda без названия." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0681a5c_2", + "metadata": {}, + "outputs": [], + "source": [ + "def multiply_numbers(first: int, second: int) -> int:\n", + " \"\"\"Перемножает два числа.\"\"\"\n", + " return first * second\n", + "\n", + "\n", + "multiply_numbers(2, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c38ff5c2", + "metadata": {}, + "outputs": [], + "source": [ + "# Структура lambda-функции:\n", + "# lambda [параметры]: [выражение]\n", + "# - ключевое слово lambda\n", + "# - передаваемые параметры\n", + "# - через двоеточие пишется исполняемое выражение\n", + "\n", + "print(\"Lambda-функции удобны для кратких операций\")" + ] + }, + { + "cell_type": "markdown", + "id": "1ea524f8", + "metadata": {}, + "source": [ + "### Lambda-функция внутри функции filter()\n", + "Lambda-функции удобно использовать с встроенными функциями." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ea524f8_2", + "metadata": {}, + "outputs": [], + "source": [ + "lambda_nums = [15, 27, 9, 18, 3, 1, 4]\n", + "\n", + "filtered_nums = list(filter(lambda num: num > 10, lambda_nums))\n", + "print(filtered_nums)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ffa83cd", + "metadata": {}, + "outputs": [], + "source": [ + "def is_greater_than_ten(number: int) -> bool:\n", + " \"\"\"Проверяет, больше ли число 10.\"\"\"\n", + " return number > 10" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9dd86bc1", + "metadata": {}, + "outputs": [], + "source": [ + "test_nums = [15, 27, 9, 18, 3, 1, 4]\n", + "result = list(filter(is_greater_than_ten, test_nums))\n", + "print(result) # [15, 27, 18]" + ] + }, + { + "cell_type": "markdown", + "id": "e87d7af0", + "metadata": {}, + "source": [ + "### Lambda-функция внутри функции sorted()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e87d7af0_2", + "metadata": {}, + "outputs": [], + "source": [ + "indices_distances = [\n", + " (901, 0.0),\n", + " (1002, 0.22982440568634488),\n", + " (442, 0.25401128310081567),\n", + "]\n", + "\n", + "sorted(indices_distances, key=lambda item: item[1], reverse=False)" + ] + }, + { + "cell_type": "markdown", + "id": "bb37c241", + "metadata": {}, + "source": [ + "## Немедленно вызываемые функции\n", + "Lambda-функции можно вызвать немедленно после определения." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb37c241_2", + "metadata": {}, + "outputs": [], + "source": [ + "# Lambda-функции рекомендуется использовать только с filter, map и sorted\n", + "# Присваивание lambda в переменную не рекомендуется\n", + "# Вместо этого используйте def для определения функций\n", + "print(\"Используйте def для определения функций вместо lambda\")" + ] + }, + { + "cell_type": "markdown", + "id": "55871e0f", + "metadata": {}, + "source": [ + "## *args и **kwargs\n", + "Они позволяют передавать функции различное количество позиционных или именованных аргументов." + ] + }, + { + "cell_type": "markdown", + "id": "24eeb967", + "metadata": {}, + "source": [ + "### *args\n", + "*args позволяет передавать переменное количество позиционных аргументов." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24eeb967_2", + "metadata": {}, + "outputs": [], + "source": [ + "def calculate_mean(*numbers: int) -> float:\n", + " \"\"\"Возвращает среднее арифметическое произвольного количества чисел.\"\"\"\n", + " total = 0\n", + " for num in numbers:\n", + " total += num\n", + " return total / len(numbers) if numbers else 0.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "316004b5", + "metadata": {}, + "outputs": [], + "source": [ + "print(calculate_mean(1, 2, 3, 4))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df85cafb", + "metadata": {}, + "outputs": [], + "source": [ + "# Главным элементом является оператор распаковки * (unpacking operator)\n", + "# Он принимает все передаваемые в функцию числа и формирует из них кортеж\n", + "\n", + "# Если захотим передать функции список, можем это сделать\n", + "numbers_list = [1, 2, 3, 4]\n", + "calculate_mean(*numbers_list)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "950a43f2", + "metadata": {}, + "outputs": [], + "source": [ + "def test_type(*numbers: int) -> None:\n", + " \"\"\"Выводит тип переданных аргументов.\"\"\"\n", + " print(numbers, type(numbers))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14680e6a", + "metadata": {}, + "outputs": [], + "source": [ + "test_type(1, 2, 3, 4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35551ebf", + "metadata": {}, + "outputs": [], + "source": [ + "nums_a = [1, 2, 3]\n", + "nums_b = [*nums_a, 4, 5, 6]\n", + "\n", + "print(nums_b)" + ] + }, + { + "cell_type": "markdown", + "id": "d260888d", + "metadata": {}, + "source": [ + "### **kwargs\n", + "**kwargs преобразует именованные параметры в словарь." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3c8f4eb", + "metadata": {}, + "outputs": [], + "source": [ + "def get_kwargs_items(**kwargs: int) -> dict[str, int]:\n", + " \"\"\"Возвращает элементы словаря.\"\"\"\n", + " return kwargs\n", + "\n", + "\n", + "get_kwargs_items(param_a=1, param_b=2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9cf780ac", + "metadata": {}, + "outputs": [], + "source": [ + "def simple_stats(*nums: float, **params: bool) -> None:\n", + " \"\"\"Выводит статистику по числам на основе флагов в params.\"\"\"\n", + " # Если ключ 'mean' есть в словаре params и его значение == True\n", + " if params.get(\"mean\", False):\n", + " # Рассчитаем среднее арифметическое кортежа nums и округлим\n", + " print(f\"mean:\\t{np.round(np.mean(nums), 3)}\")\n", + "\n", + " # Если ключ 'std' есть в словаре params и его значение == True\n", + " if params.get(\"std\", False):\n", + " # Рассчитаем СКО кортежа nums и округлим\n", + " print(f\"std:\\t{np.round(np.std(nums), 3)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92552a03", + "metadata": {}, + "outputs": [], + "source": [ + "simple_stats(5, 10, 15, 20, mean=True, std=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_6_functions.py b/python/makarov/chapter_6_functions.py new file mode 100644 index 00000000..a0e09b14 --- /dev/null +++ b/python/makarov/chapter_6_functions.py @@ -0,0 +1,406 @@ +"""Функции.""" + +import matplotlib.pyplot as plt +import numpy as np + +# ## Встроенные функции +# В Python есть множество встроенных функций: +# 1. Стандартный функционал (built-in functions) +# 2. Дополнительные библиотеки (library functions) + +np.random.seed(42) +height = list(np.round(np.random.normal(180, 10, 1000))) + +plt.hist(height, bins=10, edgecolor="black") +plt.title("Распределение роста") +plt.xlabel("Рост (см)") +plt.ylabel("Количество") +plt.show() + +# ## Параметры и аргументы функции +# - **Параметр** — это то, что запрашивает функция при вызове (например, `bins`) +# - **Аргумент** — значение этого параметра (например, `10`) + +plt.hist(bins=10, x=height) +plt.show() + +plt.hist(height) +plt.show() + +print("Первая строка") +print() +print("Третья строка") + +# ## Функции и методы +# Методы — это функции, которые можно применить только к конкретному объекту. + +some_string = "machine learning" +some_string.title() + + +# ## Собственные функции в Python +# ### Объявление и вызов функции + + +def double(value: int) -> int: + """Удваивает переданное значение.""" + return value * 2 + + +double(5) + + +# ## Пустое тело функции +# Тело функции не может быть пустым. Нужно как минимум указать `return` или `pass`. + + +def only_pass() -> None: + """Функция с pass.""" + # pass + + +only_pass() + + +# ## Функция print() вместо return +# Результат работы функции можно вывести с помощью `print()`. + + +def double_print(value: int) -> None: + """Удваивает значение и выводит результат.""" + res = value * 2 + print(res) + + +double_print(5) + + +# ### Различие между return и print +# - **return** возвращает значение функции и прерывает её работу +# - **print()** просто выводит значение и не влияет на дальнейшее исполнение кода + +# ## Параметры собственных функций +# Параметры могут быть позиционными и именованными. + + +def calc_sum(first_num: int, second_num: int) -> int: + """Возвращает сумму двух чисел.""" + return first_num + second_num + + +calc_sum(1, second_num=2) + + +# + +def calc_sum_default(first_num: int = 1, second_num: int = 2) -> int: + """Возвращает сумму двух чисел с параметрами по умолчанию.""" + return first_num + second_num + + +calc_sum_default() + + +# + +def greet() -> None: + """Выводит приветствие.""" + print("Hello, World!") + + +greet() + + +# - + +# ## Аннотация функции +# Аннотация позволяет явно прописать тип данных параметров и возвращаемых значений. + + +def convert_to_int(value: float = 3.5) -> int: + """Преобразует float в int.""" + return int(value) + + +convert_to_int.__annotations__ + +convert_to_int() + +# ## Дополнительные возможности функций + +result_arithmetic = calc_sum(1, 2) * 2 +print(result_arithmetic) + +result_logic = calc_sum(1, 2) > 2 +print(result_logic) + + +def first_letter() -> str: + """Возвращает строку 'Python'.""" + return "Python" + + +first_char = first_letter()[0] +print(first_char) + + +def square_from_input() -> int: + """Запрашивает число и возвращает его квадрат.""" + user_inp = int(input("Введите число: ")) + result_local = user_inp**2 + return result_local + + +# ## Результат вызова функции +# Функция может возвращать список, кортеж или словарь. + + +# + +def create_list(count: int) -> list[int]: + """Создает список чисел от 0 до count-1.""" + numbers = [] + for index in range(count): + numbers.append(index) + return numbers + + +create_list(5) + + +# - + + +def get_tuple_values() -> tuple[str, int]: + """Возвращает кортеж из строки и числа.""" + string_val = "Python" + number_val = 42 + return string_val, number_val + + +str_result, num_result = get_tuple_values() +print(str_result, num_result) +print(type(str_result), type(num_result)) + +tuple_result = get_tuple_values() +print(tuple_result) +print(type(tuple_result)) + + +def is_even_numbers(number: int) -> bool: + """Проверяет, четное ли число.""" + return number % 2 == 0 + + +is_even_numbers(4) + + +# ## Использование библиотек +# Внутри функций можно использовать дополнительные библиотеки Python. + + +def mean_f(data: float | list[float] | np.ndarray) -> float: # type: ignore[misc] + """Рассчитает среднее арифметическое и прибавит единицу.""" + return float(np.mean(data)) + 1 + + +data_values = [1, 2, 3] + +mean_f([float(x) for x in data_values]) + +# ## Глобальные и локальные переменные +# Переменные существуют либо только внутри функции (локальные), либо во всей программе (глобальные). + +# + +global_name = "Петр" + + +def show_name() -> None: + """Выводит глобальное имя.""" + print(global_name) + + +# - + +show_name() + + +def show_local_name() -> None: + """Выводит локальное имя.""" + local_name_var = "Алена" + print(local_name_var) + + +show_local_name() + +# + +global_local_name: str + + +def make_global() -> str: # возвращаем str + """Создает имя.""" + return "Алена" + + +global_local_name = make_global() # присваиваем +print(global_local_name) +# - + +make_global() + +print(global_local_name) + +# + +global_number = 5 + + +def print_number() -> None: + """Выводит локальное число.""" + local_number = 10 + print("Local number:", local_number) + + +# - + +print_number() + +print("Global number:", global_number) + + +# ## Анонимные или lambda-функции +# Функции можно создавать с помощью слова lambda без названия. + + +# + +def multiply_numbers(first: int, second: int) -> int: + """Перемножает два числа.""" + return first * second + + +multiply_numbers(2, 3) + +# + +# Структура lambda-функции: +# lambda [параметры]: [выражение] +# - ключевое слово lambda +# - передаваемые параметры +# - через двоеточие пишется исполняемое выражение + +print("Lambda-функции удобны для кратких операций") +# - + +# ### Lambda-функция внутри функции filter() +# Lambda-функции удобно использовать с встроенными функциями. + +# + +lambda_nums = [15, 27, 9, 18, 3, 1, 4] + +filtered_nums = list(filter(lambda num: num > 10, lambda_nums)) +print(filtered_nums) + + +# - + + +def is_greater_than_ten(number: int) -> bool: + """Проверяет, больше ли число 10.""" + return number > 10 + + +test_nums = [15, 27, 9, 18, 3, 1, 4] +result = list(filter(is_greater_than_ten, test_nums)) +print(result) # [15, 27, 18] + +# ### Lambda-функция внутри функции sorted() + +# + +indices_distances = [ + (901, 0.0), + (1002, 0.22982440568634488), + (442, 0.25401128310081567), +] + +sorted(indices_distances, key=lambda item: item[1], reverse=False) +# - + +# ## Немедленно вызываемые функции +# Lambda-функции можно вызвать немедленно после определения. + +# Lambda-функции рекомендуется использовать только с filter, map и sorted +# Присваивание lambda в переменную не рекомендуется +# Вместо этого используйте def для определения функций +print("Используйте def для определения функций вместо lambda") + + +# ## *args и **kwargs +# Они позволяют передавать функции различное количество позиционных или именованных аргументов. + +# ### *args +# *args позволяет передавать переменное количество позиционных аргументов. + + +def calculate_mean(*numbers: int) -> float: + """Возвращает среднее арифметическое произвольного количества чисел.""" + total = 0 + for num in numbers: + total += num + return total / len(numbers) if numbers else 0.0 + + +print(calculate_mean(1, 2, 3, 4)) + +# + +# Главным элементом является оператор распаковки * (unpacking operator) +# Он принимает все передаваемые в функцию числа и формирует из них кортеж + +# Если захотим передать функции список, можем это сделать +numbers_list = [1, 2, 3, 4] +calculate_mean(*numbers_list) + + +# - + + +def test_type(*numbers: int) -> None: + """Выводит тип переданных аргументов.""" + print(numbers, type(numbers)) + + +test_type(1, 2, 3, 4) + +# + +nums_a = [1, 2, 3] +nums_b = [*nums_a, 4, 5, 6] + +print(nums_b) + + +# - + +# ### **kwargs +# **kwargs преобразует именованные параметры в словарь. + + +# + +def get_kwargs_items(**kwargs: int) -> dict[str, int]: + """Возвращает элементы словаря.""" + return kwargs + + +get_kwargs_items(param_a=1, param_b=2) + + +# - + + +def simple_stats(*nums: float, **params: bool) -> None: + """Выводит статистику по числам на основе флагов в params.""" + # Если ключ 'mean' есть в словаре params и его значение == True + if params.get("mean", False): + # Рассчитаем среднее арифметическое кортежа nums и округлим + print(f"mean:\t{np.round(np.mean(nums), 3)}") + + # Если ключ 'std' есть в словаре params и его значение == True + if params.get("std", False): + # Рассчитаем СКО кортежа nums и округлим + print(f"std:\t{np.round(np.std(nums), 3)}") + + +simple_stats(5, 10, 15, 20, mean=True, std=True) diff --git a/python/makarov/chapter_7_list_tuple_set.ipynb b/python/makarov/chapter_7_list_tuple_set.ipynb new file mode 100644 index 00000000..bfeabe11 --- /dev/null +++ b/python/makarov/chapter_7_list_tuple_set.ipynb @@ -0,0 +1,1867 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "431507e3", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Списки, кортежи и множества.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "2752e5cc", + "metadata": {}, + "source": [ + "# Списки" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9c8f241", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[] []\n" + ] + } + ], + "source": [ + "# Список можно создать (или как правильнее говорить инициализировать)\n", + "# через пустые квадратные скобки [] или с помощью функции list()\n", + "\n", + "some_list_1: list[int] = []\n", + "\n", + "print(some_list_1)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "91151b8c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[3, 'число три', ['число', 'три'], {'число': 3}]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Элементом списка может быть любой объект, например,\n", + "# число, строка, список или словарь.\n", + "\n", + "number_three = [3, \"число три\", [\"число\", \"три\"], {\"число\": 3}]\n", + "number_three" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d95854a1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Длину списка можно узнать с помощью функции len().\n", + "\n", + "len(number_three)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "85356b1b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a e\n" + ] + } + ], + "source": [ + "# Индекс и срез списка\n", + "\n", + "# у списка есть положительный и отрицательный индексы\n", + "abc_list = [\"a\", \"b\", \"c\", \"d\", \"e\"]\n", + "\n", + "# воспользуемся ими для вывода первого и последнего элементов\n", + "print(abc_list[0], abc_list[-1])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ac0939ff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Игорь'" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# при работе с вложенным списком\n", + "salary_list = [[\"Анна\", 90000], [\"Игорь\", 85000], [\"Алексей\", 95000]]\n", + "\n", + "# мы вначале указываем индекс вложенного списка,\n", + "# а затем индекс элемента в нем\n", + "salary_list[1][0]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "393929ad", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# индекс можно узнать с помощью метода .index()\n", + "abc_list.index(\"c\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "58f220d1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# метод .index() можно применить и ко вложенному списку\n", + "\n", + "salary_list.index([\"Игорь\", 85000])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6c041e17", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Вт', 'Ср', 'Чт', 'Пт']" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# создадим список с днями недели\n", + "days_list = [\"Пн\", \"Вт\", \"Ср\", \"Чт\", \"Пт\", \"Сб\", \"Вс\"]\n", + "\n", + "# и выведем со второго по пятый элемент\n", + "days_list[1:5]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "fd59ca38", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Пн', 'Ср', 'Пт']" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем каждый второй элемент в срезе с первого по пятый\n", + "days_list[:5:2]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "fcb38f79", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# проверим есть ли \"Пн\" в списке\n", + "\"Пн\" in days_list" + ] + }, + { + "cell_type": "markdown", + "id": "d25ebffc", + "metadata": {}, + "source": [ + "# Добавление, замена и удаление элементов списка" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "dc7bc2b7", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим список\n", + "numbers = [1, 2, 3, 4, 5]" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "6c41ea8f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6]\n" + ] + } + ], + "source": [ + "# добавим один элемент в конец списка с помощью метода .append()\n", + "numbers.append(6)\n", + "print(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "dcadfa03", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6]\n" + ] + } + ], + "source": [ + "# добавим элемент в определенное место в списке\n", + "# через желаемый индекс этого элемента\n", + "numbers.insert(0, 0)\n", + "print(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "5aecd1dd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 10, 4, 5, 6]\n" + ] + } + ], + "source": [ + "# изменим четвертый элемент в списке\n", + "numbers[3] = 10\n", + "print(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "b2eb4da7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 4, 5, 6]\n" + ] + } + ], + "source": [ + "# удалим элемент по его значению\n", + "numbers.remove(10)\n", + "print(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "5a1cccd7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 4, 5, 6]\n" + ] + } + ], + "source": [ + "# удалим элемент по его индексу через ключевое слово del\n", + "del numbers[0]\n", + "print(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "21bb9820", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# сделаем то же самое с помощью метода .pop()\n", + "# этот метод выводит удаляемый элемент\n", + "numbers.pop(0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9cddf7cd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6]\n" + ] + } + ], + "source": [ + "# Сложение списков\n", + "# Добавить к списку еще один список можно с помощью метода .extend()\n", + "list_one = [1, 2, 3]\n", + "list_two = [4, 5, 6]\n", + "list_one.extend(list_two)\n", + "print(list_one)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "f3bf1a12", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9]\n" + ] + } + ], + "source": [ + "# Кроме того, два списка можно просто сложить (concatenate).\n", + "\n", + "list_three = [7, 8, 9]\n", + "combined_list = list_one + list_three\n", + "print(combined_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "ba6a4316", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['раз', 'раз', 'раз', 'раз', 'раз']" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# иногда бывает полезно «размножить» элементы списка.\n", + "[\"раз\"] * 5" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "cbd32a32", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['раз', 'раз', 'раз', 'два', 'два']" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Такие «произведения» также можно складывать.\n", + "\n", + "[\"раз\"] * 3 + [\"два\"] * 2" + ] + }, + { + "cell_type": "markdown", + "id": "03cd2b22", + "metadata": {}, + "source": [ + "# Распаковка списков" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "da5cff90", + "metadata": {}, + "outputs": [], + "source": [ + "# заново создадим список с днями недели\n", + "week = [\n", + " \"Понедельник\",\n", + " \"Вторник\",\n", + " \"Среда\",\n", + " \"Четверг\",\n", + " \"Пятница\",\n", + " \"Суббота\",\n", + " \"Воскресенье\",\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "69056c35", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Понедельник'" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# указав индекс элемента, его можно записать в переменную\n", + "monday = week[0]\n", + "monday" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "0f04ab0a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('Понедельник', 'Вторник', 'Среда')" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# срез можно поместить в несколько переменных\n", + "monday, tuesday, wednesday = week[:3]\n", + "\n", + "# важно, чтобы количество элементов среза было равно\n", + "# количеству переменных\n", + "monday, tuesday, wednesday" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "67b5bad6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Понедельник'" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# можно выделить первый элемент,\n", + "# а остальные поместить в переменную со звездочкой\n", + "monday, *_ = week\n", + "monday" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "8cab3c5e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('Понедельник', 'Воскресенье')" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Аналогичным образом мы можем распаковать\n", + "# первый, *остальные и последний элемент списка.\n", + "\n", + "monday, *days, sunday = week\n", + "monday, sunday" + ] + }, + { + "cell_type": "markdown", + "id": "4a5c68fc", + "metadata": {}, + "source": [ + "# Сортировка списков" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "9d7c488e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 5, 5, 6, 9]" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Для сортировки списка можно использовать функцию sorted()\n", + "# и метод .sort(). Функция sorted() не изменяет объект\n", + "# и сразу выводит результат сортировки.\n", + "\n", + "# отсортируем список по возрастанию\n", + "unsorted_list = [5, 2, 9, 1, 5, 6]\n", + "sorted(unsorted_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "9c9180b6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[5, 2, 9, 1, 5, 6]" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# исходный список остается прежним\n", + "unsorted_list" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "768aee7c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 5, 5, 6, 9]" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Впрочем, если записать результат вызова функции sorted()\n", + "# в переменную, изменение станет постоянным\n", + "sorted_list = sorted(unsorted_list)\n", + "sorted_list" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "5fdbab3c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[9, 6, 5, 5, 2, 1]" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# метод .sort() сохраняет результат, но не выводит его сразу\n", + "# reverse = True задает сортировку по убыванию\n", + "unsorted_list.sort(reverse=True)\n", + "unsorted_list" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "ea38efd5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[9, 6, 5, 5, 2, 1]" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем результат\n", + "unsorted_list" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "58f37baa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 5, 5, 6, 9]" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# метод .reverse() задает обратный порядок,\n", + "# сохраняет, но не выводит результат\n", + "unsorted_list.reverse()\n", + "# выведем результат\n", + "unsorted_list" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "68cfe18f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"\"" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# функция reversed() возвращает итератор\n", + "type(reversed(unsorted_list))" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "76910306", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[9, 6, 5, 5, 2, 1]" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# вывести результат можно с помощью функции list()\n", + "list(reversed(unsorted_list))" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "6837e014", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 5, 5, 6, 9]" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# результат при этом не сохраняется\n", + "unsorted_list" + ] + }, + { + "cell_type": "markdown", + "id": "3eb0cbb9", + "metadata": {}, + "source": [ + "# Преобразование списка в строку" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "9e4e2a82", + "metadata": {}, + "outputs": [], + "source": [ + "# дан список из строковых элементов\n", + "str_list = [\"P\", \"y\", \"t\", \"h\", \"o\", \"n\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "44dbbb7f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Python'" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# с помощью метода .join() можно соединить все элементы\n", + "joined_str = \"\".join(str_list)\n", + "joined_str" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "1d3e3b79", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'P-y-t-h-o-n'" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# если в кавычках ничего не указывать, элементы просто соединятся,\n", + "# но можно указать любой другой элемент\n", + "joined_str_with_dash = \"-\".join(str_list)\n", + "joined_str_with_dash" + ] + }, + { + "cell_type": "markdown", + "id": "c0d3d92b", + "metadata": {}, + "source": [ + "# Арифметика в списках" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "df10953d", + "metadata": {}, + "outputs": [], + "source": [ + "# дан список чисел\n", + "nums_ = [3, 2, 1, 4, 5, 12, 3, 3, 7, 9, 11, 15]" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "52ca2668", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# с помощью метода .count() мы можем посчитать\n", + "# частоту вхождения элемента в список\n", + "nums_.count(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "047b9597", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, 15, 75)" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# кроме того мы можем найти минимальное и максимальное значения\n", + "# и сумму элементов\n", + "result = (min(nums_), max(nums_), sum(nums_))\n", + "result" + ] + }, + { + "cell_type": "markdown", + "id": "60d96929", + "metadata": {}, + "source": [ + "# List comprehension" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "873f0add", + "metadata": {}, + "outputs": [], + "source": [ + "# А теперь рассмотрим list comprehension. По сути, list comprehension\n", + "# позволяет превратить один список в другой, преобразовывая и отбирая\n", + "# элементы исходного списка." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "9c111086", + "metadata": {}, + "outputs": [], + "source": [ + "# дан список имен\n", + "names = [\"Артем\", \"Антон\", \"Александр\", \"Борис\", \"Виктор\", \"Геннадий\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "54a0a06b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Артем', 'Антон', 'Александр']" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# оставим имена, начинающиеся с буквы \"А\"\n", + "\n", + "a_names = [name for name in names if name.startswith(\"А\")]\n", + "a_names" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "b4d445e4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['артем', 'антон', 'александр', 'борис', 'виктор', 'геннадий']" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# переведем все буквы в строчные, условие здесь не нужно\n", + "lower_names = [name.lower() for name in names]\n", + "lower_names" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "b57565c0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Артем', 'Антон', 'Александр', 'Борис', 'Вадим', 'Геннадий']" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# схема условия if-else немного отличается\n", + "# оставляем имя, если это не Виктор, если Виктор - заменяем на Вадим\n", + "replace_name = [name if name != \"Виктор\" else \"Вадим\" for name in names]\n", + "replace_name" + ] + }, + { + "cell_type": "markdown", + "id": "556c194e", + "metadata": {}, + "source": [ + "# Кортежи" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "848543e0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "() ()\n" + ] + } + ], + "source": [ + "# Кортеж (tuple) инициализируется при помощи круглых скобок ()\n", + "# или функции tuple().\n", + "\n", + "# создадим две переменные и поместим в них пустые кортежи\n", + "tuple_1: tuple[str, ...] = ()\n", + "tuple_2: tuple[str, ...] = ()\n", + "print(tuple_1, tuple_2)" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "0d1a2c17", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'a'" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Во многом кортеж похож на список.\n", + "# Это также упорядоченный набор элементов с индексом,\n", + "# начинающимся с нуля.\n", + "\n", + "# создадим кортеж\n", + "letters: tuple[str, ...] = (\"a\", \"b\", \"c\")\n", + "\n", + "# и выведем его первый элемент\n", + "letters[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "dc0b84e3", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'tuple' object does not support item assignment", + "output_type": "error", + "traceback": [ + "TypeError: 'tuple' object does not support item assignment" + ] + } + ], + "source": [ + "# но изменить элемент, как мы это делали в списке, нельзя\n", + "# letters[0] = 'd'" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "bc545b0d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['d', 'b', 'c']" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# для изменения элемента кортеж вначале нужно преобразовать в список\n", + "letters_list: list[str] = list(letters)\n", + "letters_list[0] = \"d\"\n", + "letters_list" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "e9839195", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tuple" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# кортеж из одного элемента можно создать с помощью запятой\n", + "single_letter: tuple[str] = (\"a\",)\n", + "type(single_letter)" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "efdba9ca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "str" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# если запятую не указывать, получится строка\n", + "not_tuple: str = \"a\"\n", + "type(not_tuple)" + ] + }, + { + "cell_type": "markdown", + "id": "63c45bd4", + "metadata": {}, + "source": [ + "# Функция enumerate()" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "49b6dcce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(0, 'Microsoft')\n", + "(1, 'Apple')\n", + "(2, 'Tesla')\n" + ] + } + ], + "source": [ + "# Если в цикле с функцией enumerate() использовать не две,\n", + "# как мы делали раньше, а одну переменную, результатом ее работы\n", + "# будет кортеж, состоящий из индекса и соответствующего элемента\n", + "# списка.\n", + "\n", + "# создадим список с названием трех компаний\n", + "companies = [\"Microsoft\", \"Apple\", \"Tesla\"]\n", + "\n", + "# и в цикле поместим результат работы функции enumerate()\n", + "# в одну переменную company\n", + "for company_item in enumerate(companies):\n", + " print(company_item)" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "92a86c4d", + "metadata": {}, + "outputs": [], + "source": [ + "# Просмотр элементов словаря\n", + "\n", + "# Аналогично, если в цикле for к словарю применить знакомый нам\n", + "# метод .items() и использовать только одну переменную,\n", + "# мы получим кортежи из ключа и значения.\n", + "\n", + "shopping_dict = {\"огурцы\": 2, \"помидоры\": 3, \"лук\": 1, \"картофель\": 2}" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "f8ec3d65", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('огурцы', 2)\n", + "('помидоры', 3)\n", + "('лук', 1)\n", + "('картофель', 2)\n" + ] + } + ], + "source": [ + "# то же самое со словарем и методом .items()\n", + "for item in shopping_dict.items():\n", + " print(item)" + ] + }, + { + "cell_type": "markdown", + "id": "5a81ece2", + "metadata": {}, + "source": [ + "# Распаковка кортежей" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "6ca00d0f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a\n" + ] + } + ], + "source": [ + "# Распаковать кортеж — значит поместить каждый\n", + "# из его элементов в отдельную переменную.\n", + "\n", + "# если в кортеже три элемента, то и переменных должно быть три\n", + "letter_a, letter_b, letter_c = (\"a\", \"b\", \"c\")\n", + "\n", + "# выведем переменную letter_a\n", + "print(letter_a)" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "263961ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 Microsoft\n", + "1 Apple\n", + "2 Tesla\n" + ] + } + ], + "source": [ + "# снова возьмем список компаний\n", + "companies = [\"Microsoft\", \"Apple\", \"Tesla\"]\n", + "\n", + "# однако с функцией enumerate() используем две переменные\n", + "for index, company in enumerate(companies):\n", + " print(index, company)" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "ed32ea95", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "огурцы 2\n", + "помидоры 3\n", + "лук 1\n", + "картофель 2\n" + ] + } + ], + "source": [ + "# С элементами словаря получается то же самое.\n", + "\n", + "shopping_dict = {\"огурцы\": 2, \"помидоры\": 3, \"лук\": 1, \"картофель\": 2}\n", + "\n", + "# используем две переменные с методом .items()\n", + "for key, value in shopping_dict.items():\n", + " print(key, value)" + ] + }, + { + "cell_type": "markdown", + "id": "dfe585bb", + "metadata": {}, + "source": [ + "# Создание кортежа через функцию zip()" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "ebf57c7f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "zip" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# если есть два и более списка\n", + "names = [\"Артем\", \"Антон\", \"Александр\", \"Борис\", \"Виктор\", \"Геннадий\"]\n", + "income = [97000, 110000, 95000, 84000, 140000, 120000]\n", + "\n", + "# функция zip() соединит первые элементы списков,\n", + "# вторые элементы списков и т.д.\n", + "type(zip(names, income))" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "d10097b1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('Артем', 97000), ('Антон', 110000), ('Александр', 95000), ('Борис', 84000), ('Виктор', 140000), ('Геннадий', 120000)]" + ] + }, + "execution_count": 80, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# для вывода результата нужно передать zip-объект в функцию list()\n", + "# на выходе мы получим список из кортежей\n", + "list(zip(names, income))" + ] + }, + { + "cell_type": "markdown", + "id": "d9bc4449", + "metadata": {}, + "source": [ + "# Множества" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da757c25", + "metadata": {}, + "outputs": [], + "source": [ + "# элементы множества изменять нельзя;\n", + "# множество — это набор уникальных элементов, а значит повторы\n", + "# в нем удаляются;\n", + "# у множества нет индекса, элементы не упорядочены." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "83e9fcd4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "set() {'a', 'c', 'b'} {'a', 'c', 'b'}\n" + ] + } + ], + "source": [ + "# Создание множества\n", + "# пустое множество задается через функцию set()\n", + "set_1: set[str] = set()\n", + "\n", + "# непустое множество задается через функцию set() и список элементов\n", + "set_2: set[str] = {\"a\", \"b\", \"c\"}\n", + "\n", + "# или путем перечисления элементов в фигурных скобках {}\n", + "set_3: set[str] = {\"a\", \"b\", \"c\"}\n", + "\n", + "# множество содержит только уникальные элементы,\n", + "# поэтому дубликаты удаляются\n", + "print(set_1, set_2, set_3)" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "c43fb886", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict" + ] + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# создать множество через пустые фигурные скобки нельзя\n", + "not_a_set: dict[str, str] = {}\n", + "\n", + "# так создается словарь\n", + "type(not_a_set)" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "d09bae89", + "metadata": {}, + "outputs": [], + "source": [ + "# Добавление и удаление элементов\n", + "# предположим, что мы хотим создать множество гласных букв\n", + "# в русском языке\n", + "vowels: set[str] = {\"а\", \"о\", \"э\", \"е\", \"у\", \"ё\", \"ю\"}" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "f2b777b4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'а', 'е', 'о', 'у', 'э', 'ю', 'я', 'ё'}" + ] + }, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# добавим одну букву \"я\" методом .add()\n", + "vowels.add(\"я\")\n", + "vowels" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "ecede16d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'а', 'е', 'и', 'о', 'у', 'ы', 'э', 'ю', 'я', 'ё'}" + ] + }, + "execution_count": 85, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# добавим две буквы \"и\" и \"ы\" методом .update()\n", + "vowels.update([\"и\", \"ы\"])\n", + "vowels" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "afc2b796", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'а', 'е', 'и', 'о', 'у', 'щ', 'ы', 'э', 'ю', 'я', 'ё'}" + ] + }, + "execution_count": 86, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# если по ошибке мы добавим согласную букву,\n", + "vowels.add(\"щ\")\n", + "vowels" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "088e0e8c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'а', 'е', 'и', 'о', 'у', 'ы', 'э', 'ю', 'я', 'ё'}" + ] + }, + "execution_count": 87, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# ее можно удалить методом .remove()\n", + "vowels.remove(\"щ\")\n", + "vowels" + ] + }, + { + "cell_type": "markdown", + "id": "5311df68", + "metadata": {}, + "source": [ + "# Теория множеств в Питоне" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "25998782", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# два множества равны, если содержат одинаковые элементы,\n", + "# при этом порядок элементов не важен\n", + "{\"a\", \"b\", \"c\"} == {\"c\", \"b\", \"a\"}" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "9f3f8a7e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 89, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# выведем мощность множества с помощью функции len()\n", + "len({\"a\", \"b\", \"c\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "a6db8bc0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 90, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# проверим, содержится ли элемент во множестве\n", + "\"a\" in {\"a\", \"b\", \"c\"}" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "57c05eda", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# возможна и обратная операция\n", + "\"a\" not in {\"a\", \"b\", \"c\"}" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "2fabbc12", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# проверим является ли А подмножеством В\n", + "set_a: set[str] = {\"a\", \"b\", \"c\"}\n", + "set_b: set[str] = {\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"}\n", + "\n", + "set_a.issubset(set_b)" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "95192e69", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 93, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# проверим является ли B надмножеством А\n", + "set_b.issuperset(set_a)" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "dcbb227c", + "metadata": {}, + "outputs": [], + "source": [ + "# даны участники команд по обработке естественного языка (nlp)\n", + "# и компьютерному зрению (cv)\n", + "nlp: set[str] = {\"Анна\", \"Николай\", \"Павел\", \"Оксана\"}\n", + "cv: set[str] = {\"Николай\", \"Евгений\", \"Ольга\", \"Оксана\"}" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "874d0187", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Оксана', 'Николай', 'Павел', 'Анна', 'Евгений', 'Ольга'}\n", + "{'Оксана', 'Николай', 'Павел', 'Анна', 'Евгений', 'Ольга'}\n" + ] + } + ], + "source": [ + "# найдем тех, кто работает или в nlp, или в cv, или в обеих командах\n", + "print(nlp.union(cv))\n", + "\n", + "# или символ |\n", + "print(nlp | cv)" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "f25e7aa7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Оксана', 'Николай'}\n", + "{'Оксана', 'Николай'}\n" + ] + } + ], + "source": [ + "# найдем тех, кто работает и в nlp, и в cv одновременно\n", + "print(nlp.intersection(cv))\n", + "\n", + "# или символ &\n", + "print(nlp & cv)" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "c5e97f2e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Павел', 'Анна'}\n", + "{'Павел', 'Анна'}\n" + ] + } + ], + "source": [ + "# найдем тех, кто работает только в nlp\n", + "print(nlp.difference(cv))\n", + "\n", + "# или символ -\n", + "print(nlp - cv)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_7_list_tuple_set.py b/python/makarov/chapter_7_list_tuple_set.py new file mode 100644 index 00000000..6e99fa21 --- /dev/null +++ b/python/makarov/chapter_7_list_tuple_set.py @@ -0,0 +1,474 @@ +"""Списки, кортежи и множества.""" + +# # Списки + +# + +# Список можно создать (или как правильнее говорить инициализировать) +# через пустые квадратные скобки [] или с помощью функции list() + +some_list_1: list[int] = [] + +print(some_list_1) + +# + +# Элементом списка может быть любой объект, например, +# число, строка, список или словарь. + +number_three = [3, "число три", ["число", "три"], {"число": 3}] +number_three + +# + +# Длину списка можно узнать с помощью функции len(). + +len(number_three) + +# + +# Индекс и срез списка + +# у списка есть положительный и отрицательный индексы +abc_list = ["a", "b", "c", "d", "e"] + +# воспользуемся ими для вывода первого и последнего элементов +print(abc_list[0], abc_list[-1]) + +# + +# при работе с вложенным списком +salary_list = [["Анна", 90000], ["Игорь", 85000], ["Алексей", 95000]] + +# мы вначале указываем индекс вложенного списка, +# а затем индекс элемента в нем +salary_list[1][0] +# - + +# индекс можно узнать с помощью метода .index() +abc_list.index("c") + +# + +# метод .index() можно применить и ко вложенному списку + +salary_list.index(["Игорь", 85000]) + +# + +# создадим список с днями недели +days_list = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"] + +# и выведем со второго по пятый элемент +days_list[1:5] +# - + +# выведем каждый второй элемент в срезе с первого по пятый +days_list[:5:2] + +# проверим есть ли "Пн" в списке +"Пн" in days_list + +# # Добавление, замена и удаление элементов списка + +# создадим список +numbers = [1, 2, 3, 4, 5] + +# добавим один элемент в конец списка с помощью метода .append() +numbers.append(6) +print(numbers) + +# добавим элемент в определенное место в списке +# через желаемый индекс этого элемента +numbers.insert(0, 0) +print(numbers) + +# изменим четвертый элемент в списке +numbers[3] = 10 +print(numbers) + +# удалим элемент по его значению +numbers.remove(10) +print(numbers) + +# удалим элемент по его индексу через ключевое слово del +del numbers[0] +print(numbers) + +# сделаем то же самое с помощью метода .pop() +# этот метод выводит удаляемый элемент +numbers.pop(0) + +# Сложение списков +# Добавить к списку еще один список можно с помощью метода .extend() +list_one = [1, 2, 3] +list_two = [4, 5, 6] +list_one.extend(list_two) +print(list_one) + +# + +# Кроме того, два списка можно просто сложить (concatenate). + +list_three = [7, 8, 9] +combined_list = list_one + list_three +print(combined_list) +# - + +# иногда бывает полезно «размножить» элементы списка. +["раз"] * 5 + +# + +# Такие «произведения» также можно складывать. + +["раз"] * 3 + ["два"] * 2 +# - + +# # Распаковка списков + +# заново создадим список с днями недели +week = [ + "Понедельник", + "Вторник", + "Среда", + "Четверг", + "Пятница", + "Суббота", + "Воскресенье", +] + +# указав индекс элемента, его можно записать в переменную +monday = week[0] +monday + +# + +# срез можно поместить в несколько переменных +monday, tuesday, wednesday = week[:3] + +# важно, чтобы количество элементов среза было равно +# количеству переменных +monday, tuesday, wednesday +# - + +# можно выделить первый элемент, +# а остальные поместить в переменную со звездочкой +monday, *_ = week +monday + +# + +# Аналогичным образом мы можем распаковать +# первый, *остальные и последний элемент списка. + +monday, *days, sunday = week +monday, sunday +# - + +# # Сортировка списков + +# + +# Для сортировки списка можно использовать функцию sorted() +# и метод .sort(). Функция sorted() не изменяет объект +# и сразу выводит результат сортировки. + +# отсортируем список по возрастанию +unsorted_list = [5, 2, 9, 1, 5, 6] +sorted(unsorted_list) +# - + +# исходный список остается прежним +unsorted_list + +# Впрочем, если записать результат вызова функции sorted() +# в переменную, изменение станет постоянным +sorted_list = sorted(unsorted_list) +sorted_list + +# метод .sort() сохраняет результат, но не выводит его сразу +# reverse = True задает сортировку по убыванию +unsorted_list.sort(reverse=True) +unsorted_list + +# выведем результат +unsorted_list + +# метод .reverse() задает обратный порядок, +# сохраняет, но не выводит результат +unsorted_list.reverse() +# выведем результат +unsorted_list + +# функция reversed() возвращает итератор +type(reversed(unsorted_list)) + +# вывести результат можно с помощью функции list() +list(reversed(unsorted_list)) + +# результат при этом не сохраняется +unsorted_list + +# # Преобразование списка в строку + +# дан список из строковых элементов +str_list = ["P", "y", "t", "h", "o", "n"] + +# с помощью метода .join() можно соединить все элементы +joined_str = "".join(str_list) +joined_str + +# если в кавычках ничего не указывать, элементы просто соединятся, +# но можно указать любой другой элемент +joined_str_with_dash = "-".join(str_list) +joined_str_with_dash + +# # Арифметика в списках + +# дан список чисел +nums_ = [3, 2, 1, 4, 5, 12, 3, 3, 7, 9, 11, 15] + +# с помощью метода .count() мы можем посчитать +# частоту вхождения элемента в список +nums_.count(3) + +# кроме того мы можем найти минимальное и максимальное значения +# и сумму элементов +result = (min(nums_), max(nums_), sum(nums_)) +result + +# # List comprehension + +# + +# А теперь рассмотрим list comprehension. По сути, list comprehension +# позволяет превратить один список в другой, преобразовывая и отбирая +# элементы исходного списка. +# - + +# дан список имен +names = ["Артем", "Антон", "Александр", "Борис", "Виктор", "Геннадий"] + +# + +# оставим имена, начинающиеся с буквы "А" + +a_names = [name for name in names if name.startswith("А")] +a_names +# - + +# переведем все буквы в строчные, условие здесь не нужно +lower_names = [name.lower() for name in names] +lower_names + +# схема условия if-else немного отличается +# оставляем имя, если это не Виктор, если Виктор - заменяем на Вадим +replace_name = [name if name != "Виктор" else "Вадим" for name in names] +replace_name + +# # Кортежи + +# + +# Кортеж (tuple) инициализируется при помощи круглых скобок () +# или функции tuple(). + +# создадим две переменные и поместим в них пустые кортежи +tuple_1: tuple[str, ...] = () +tuple_2: tuple[str, ...] = () +print(tuple_1, tuple_2) + +# + +# Во многом кортеж похож на список. +# Это также упорядоченный набор элементов с индексом, +# начинающимся с нуля. + +# создадим кортеж +letters: tuple[str, ...] = ("a", "b", "c") + +# и выведем его первый элемент +letters[0] + +# + +# но изменить элемент, как мы это делали в списке, нельзя +# letters[0] = 'd' +# - + +# для изменения элемента кортеж вначале нужно преобразовать в список +letters_list: list[str] = list(letters) +letters_list[0] = "d" +letters_list + +# кортеж из одного элемента можно создать с помощью запятой +single_letter: tuple[str] = ("a",) +type(single_letter) + +# если запятую не указывать, получится строка +not_tuple: str = "a" +type(not_tuple) + +# # Функция enumerate() + +# + +# Если в цикле с функцией enumerate() использовать не две, +# как мы делали раньше, а одну переменную, результатом ее работы +# будет кортеж, состоящий из индекса и соответствующего элемента +# списка. + +# создадим список с названием трех компаний +companies = ["Microsoft", "Apple", "Tesla"] + +# и в цикле поместим результат работы функции enumerate() +# в одну переменную company +for company_item in enumerate(companies): + print(company_item) + +# + +# Просмотр элементов словаря + +# Аналогично, если в цикле for к словарю применить знакомый нам +# метод .items() и использовать только одну переменную, +# мы получим кортежи из ключа и значения. + +shopping_dict = {"огурцы": 2, "помидоры": 3, "лук": 1, "картофель": 2} +# - + +# то же самое со словарем и методом .items() +for item in shopping_dict.items(): + print(item) + +# # Распаковка кортежей + +# + +# Распаковать кортеж — значит поместить каждый +# из его элементов в отдельную переменную. + +# если в кортеже три элемента, то и переменных должно быть три +letter_a, letter_b, letter_c = ("a", "b", "c") + +# выведем переменную letter_a +print(letter_a) + +# + +# снова возьмем список компаний +companies = ["Microsoft", "Apple", "Tesla"] + +# однако с функцией enumerate() используем две переменные +for index, company in enumerate(companies): + print(index, company) + +# + +# С элементами словаря получается то же самое. + +shopping_dict = {"огурцы": 2, "помидоры": 3, "лук": 1, "картофель": 2} + +# используем две переменные с методом .items() +for key, value in shopping_dict.items(): + print(key, value) +# - + +# # Создание кортежа через функцию zip() + +# + +# если есть два и более списка +names = ["Артем", "Антон", "Александр", "Борис", "Виктор", "Геннадий"] +income = [97000, 110000, 95000, 84000, 140000, 120000] + +# функция zip() соединит первые элементы списков, +# вторые элементы списков и т.д. +type(zip(names, income)) +# - + +# для вывода результата нужно передать zip-объект в функцию list() +# на выходе мы получим список из кортежей +list(zip(names, income)) + +# # Множества + +# + +# элементы множества изменять нельзя; +# множество — это набор уникальных элементов, а значит повторы +# в нем удаляются; +# у множества нет индекса, элементы не упорядочены. + +# + +# Создание множества +# пустое множество задается через функцию set() +set_1: set[str] = set() + +# непустое множество задается через функцию set() и список элементов +set_2: set[str] = {"a", "b", "c"} + +# или путем перечисления элементов в фигурных скобках {} +set_3: set[str] = {"a", "b", "c"} + +# множество содержит только уникальные элементы, +# поэтому дубликаты удаляются +print(set_1, set_2, set_3) + +# + +# создать множество через пустые фигурные скобки нельзя +not_a_set: dict[str, str] = {} + +# так создается словарь +type(not_a_set) +# - + +# Добавление и удаление элементов +# предположим, что мы хотим создать множество гласных букв +# в русском языке +vowels: set[str] = {"а", "о", "э", "е", "у", "ё", "ю"} + +# добавим одну букву "я" методом .add() +vowels.add("я") +vowels + +# добавим две буквы "и" и "ы" методом .update() +vowels.update(["и", "ы"]) +vowels + +# если по ошибке мы добавим согласную букву, +vowels.add("щ") +vowels + +# ее можно удалить методом .remove() +vowels.remove("щ") +vowels + +# # Теория множеств в Питоне + +# два множества равны, если содержат одинаковые элементы, +# при этом порядок элементов не важен +{"a", "b", "c"} == {"c", "b", "a"} + +# выведем мощность множества с помощью функции len() +len({"a", "b", "c"}) + +# проверим, содержится ли элемент во множестве +"a" in {"a", "b", "c"} + +# возможна и обратная операция +"a" not in {"a", "b", "c"} + +# + +# проверим является ли А подмножеством В +set_a: set[str] = {"a", "b", "c"} +set_b: set[str] = {"a", "b", "c", "d", "e", "f"} + +set_a.issubset(set_b) +# - + +# проверим является ли B надмножеством А +set_b.issuperset(set_a) + +# даны участники команд по обработке естественного языка (nlp) +# и компьютерному зрению (cv) +nlp: set[str] = {"Анна", "Николай", "Павел", "Оксана"} +cv: set[str] = {"Николай", "Евгений", "Ольга", "Оксана"} + +# + +# найдем тех, кто работает или в nlp, или в cv, или в обеих командах +print(nlp.union(cv)) + +# или символ | +print(nlp | cv) + +# + +# найдем тех, кто работает и в nlp, и в cv одновременно +print(nlp.intersection(cv)) + +# или символ & +print(nlp & cv) + +# + +# найдем тех, кто работает только в nlp +print(nlp.difference(cv)) + +# или символ - +print(nlp - cv) diff --git a/python/makarov/chapter_8_dict.ipynb b/python/makarov/chapter_8_dict.ipynb new file mode 100644 index 00000000..580370da --- /dev/null +++ b/python/makarov/chapter_8_dict.ipynb @@ -0,0 +1,1220 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "cf2890c1", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Словарь.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "2b4897e1", + "metadata": {}, + "source": [ + "# Понятие словаря в Питоне" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "20bd2563", + "metadata": {}, + "outputs": [], + "source": [ + "# пустой словарь можно создать с помощью {} или функции dict()\n", + "# Способ 3. Класс Counter модуля collections\n", + "from collections import Counter\n", + "\n", + "# Небольшое отступление от темы. Сложные структуры данных бывает\n", + "# удобно вывести с помощью функции pprint() одноименного модуля.\n", + "from pprint import pprint\n", + "\n", + "import numpy as np\n", + "\n", + "empty_dict1: dict[str, int] = {}\n", + "empty_dict2: dict[str, int] = {}\n", + "print(empty_dict1) # {}\n", + "print(empty_dict2) # {}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2123635a", + "metadata": {}, + "outputs": [], + "source": [ + "# словарь можно сразу заполнить ключами и значениями\n", + "person1: dict[str, str | int] = {\"name\": \"Alice\", \"age\": 30, \"city\": \"New York\"}\n", + "person1" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "befa1e19", + "metadata": {}, + "outputs": [], + "source": [ + "# словарь можно создать из вложенных кортежей\n", + "nested_list: list[tuple[str, int]] = [(\"a\", 1), (\"b\", 2), (\"c\", 3)]\n", + "dict_from_nested_list: dict[str, int] = dict(nested_list)\n", + "dict_from_nested_list" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "90d591f1", + "metadata": {}, + "outputs": [], + "source": [ + "# если поместить ключи в кортеж и задать значение\n", + "keys1: tuple[str, ...] = (\"name\", \"age\", \"city\")\n", + "value: int = 0\n", + "person2: dict[str, int] = dict.fromkeys(keys1, value)\n", + "person2" + ] + }, + { + "cell_type": "markdown", + "id": "3172bee9", + "metadata": {}, + "source": [ + "# Ключи и значения словаря" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e7485b3c", + "metadata": {}, + "outputs": [], + "source": [ + "# Ключами словаря могут быть только неизменяемые типы данных.\n", + "# Например, строки, числа, кортежи или логические значения\n", + "# (Boolean). Кроме того, ключи должны быть уникальными и\n", + "# соответственно не могут повторяться.\n", + "\n", + "# Значения словаря, наоборот, могут состоять из чисел, строк,\n", + "# пропущенных (NaN) и логических значений, значения типа None,\n", + "# списков, массивов Numpy и вложенных словарей." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4290b2d0", + "metadata": {}, + "outputs": [], + "source": [ + "# приведем пример того, какими могут быть значения словаря\n", + "value_types: dict[str, object] = {\n", + " \"k1\": 123,\n", + " \"k2\": \"string\",\n", + " \"k3\": np.nan, # тип \"Пропущенное значение\"\n", + " \"k4\": True, # логическое значение\n", + " \"k5\": None,\n", + " \"k6\": [1, 2, 3],\n", + " \"k7\": np.array([1, 2, 3]),\n", + " \"k8\": {1: \"v1\", 2: \"v2\", 3: \"v3\"},\n", + "}\n", + "value_types" + ] + }, + { + "cell_type": "markdown", + "id": "d327e093", + "metadata": {}, + "source": [ + "# Методы .keys(), .values() и .items()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2ddd9c3f", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим несложный словарь с информацией о сотруднике\n", + "person: dict[str, str | int] = {\n", + " \"name\": \"John Doe\",\n", + " \"age\": 28,\n", + " \"position\": \"Software Engineer\",\n", + " \"department\": \"IT\",\n", + "}\n", + "person" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "733145c5", + "metadata": {}, + "outputs": [], + "source": [ + "# посмотрим на ключи словаря\n", + "person.keys()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "ef8b8ee6", + "metadata": {}, + "outputs": [], + "source": [ + "# значения\n", + "person.values()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "55bb682b", + "metadata": {}, + "outputs": [], + "source": [ + "# а также на пары ключ-значение в виде списка из кортежей\n", + "person.items()" + ] + }, + { + "cell_type": "markdown", + "id": "62056afb", + "metadata": {}, + "source": [ + "# Использование цикла for" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e505a2f", + "metadata": {}, + "outputs": [], + "source": [ + "# Ключи и значения словаря удобно просматривать с помощью\n", + "# цикла for и метода .items().\n", + "for key, person_value in person.items():\n", + " print(f\"Key: {key}, Value: {person_value}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "baecb6d1", + "metadata": {}, + "outputs": [], + "source": [ + "# Доступ по ключу и метод .get()\n", + "# Конкретное значение в словаре можно получить, введя название\n", + "# словаря и затем название ключа в квадратных скобках.\n", + "person[\"name\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0e4f5679", + "metadata": {}, + "outputs": [], + "source": [ + "# Если такого ключа нет, Питон выдаст ошибку.\n", + "# Для того чтобы этого не произошло, можно использовать метод\n", + "# .get(). Он также выводит значение по ключу.\n", + "person.get(\"name\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "8e7c60a8", + "metadata": {}, + "outputs": [], + "source": [ + "# Если ключа в словаре нет, метод .get() возвращает значение\n", + "# None.\n", + "print(person.get(\"salary\")) # ключа \"salary\" нет в словаре" + ] + }, + { + "cell_type": "markdown", + "id": "d38ab847", + "metadata": {}, + "source": [ + "# Проверка наличия ключа и значения в словаре" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "3bba5e00", + "metadata": {}, + "outputs": [], + "source": [ + "# С помощью оператора in мы можем проверить наличие\n", + "# определенного ключа в словаре.\n", + "print(\"age\" in person) # True\n", + "print(\"salary\" in person) # False" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "659cdb47", + "metadata": {}, + "outputs": [], + "source": [ + "# Метод .items() поможет проверить наличие пары ключ : значение.\n", + "# Обратите внимание, эту пару мы записываем в форме кортежа.\n", + "print((\"age\", 28) in person.items()) # True" + ] + }, + { + "cell_type": "markdown", + "id": "1c2994ff", + "metadata": {}, + "source": [ + "# Операции со словарями" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "0f68c3a9", + "metadata": {}, + "outputs": [], + "source": [ + "# Добавление и изменение элементов\n", + "# Добавить элемент можно, передав новому ключу новое значение.\n", + "person[\"salary\"] = 70000\n", + "person" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "43b07a49", + "metadata": {}, + "outputs": [], + "source": [ + "# Изменить элемент можно передав существующему ключу новое\n", + "# значение.\n", + "person[\"age\"] = 29\n", + "person" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "59408413", + "metadata": {}, + "outputs": [], + "source": [ + "# Метод .update() позволяет соединить два словаря.\n", + "additional_info: dict[str, str] = {\"email\": \"john.doe@example.com\"}" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "1b2adaf2", + "metadata": {}, + "outputs": [], + "source": [ + "# и присоединим его к существующему словарю с помощью метода\n", + "# .update()\n", + "person.update(additional_info)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "fdcec0cb", + "metadata": {}, + "outputs": [], + "source": [ + "person" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "0eb119d2", + "metadata": {}, + "outputs": [], + "source": [ + "# Метод .setdefault() не изменяет значение, если указанный ключ\n", + "# уже содержится в словаре.\n", + "person.setdefault(\"age\", 35) # ключ \"age\" уже есть в словаре\n", + "person" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "987da4a9", + "metadata": {}, + "outputs": [], + "source": [ + "# Если такого ключа нет, ключ и соответствующее значение будут\n", + "# добавлены в словарь.\n", + "person.setdefault(\"phone\", \"123-456-7890\")\n", + "person" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "5beb5803", + "metadata": {}, + "outputs": [], + "source": [ + "# Удаление элементов\n", + "# Метод .pop() удаляет элемент по ключу и выводит удаляемое\n", + "# значение.\n", + "removed_value: str | int = person.pop(\"salary\")\n", + "print(f\"Removed value: {removed_value}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "f0c2398e", + "metadata": {}, + "outputs": [], + "source": [ + "# Убедимся, что этой пары ключа и значения больше нет в\n", + "# словаре.\n", + "person" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "d34a64f6", + "metadata": {}, + "outputs": [], + "source": [ + "# Ключевое слово del также удаляет элемент по ключу.\n", + "del person[\"phone\"]\n", + "person" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "c8f79913", + "metadata": {}, + "outputs": [], + "source": [ + "# Метод .popitem() удаляет и выводит последний добавленный в\n", + "# словарь элемент.\n", + "removed_item: tuple[str, str | int] = person.popitem()\n", + "print(f\"Removed item: {removed_item}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "15860a62", + "metadata": {}, + "outputs": [], + "source": [ + "# Метод .clear() удаляет все ключи и значения и возвращает\n", + "# пустой словарь.\n", + "person.clear()\n", + "person" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "60a8c2a6", + "metadata": {}, + "outputs": [], + "source": [ + "# удалим весь словарь\n", + "del person\n", + "\n", + "# если попытаться вновь вызвать эту переменную, Питон выдаст\n", + "# ошибку" + ] + }, + { + "cell_type": "markdown", + "id": "517c0952", + "metadata": {}, + "source": [ + "# Сортировка словарей" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "055ee809", + "metadata": {}, + "outputs": [], + "source": [ + "# Для сортировки словарей можно использовать функцию sorted().\n", + "# возьмем несложный словарь\n", + "dict_to_sort: dict[str, int] = {\n", + " \"banana\": 3,\n", + " \"apple\": 5,\n", + " \"orange\": 2,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "05f689b1", + "metadata": {}, + "outputs": [], + "source": [ + "# Отсортируем ключи этого словаря.\n", + "sorted(dict_to_sort)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "56fa3da6", + "metadata": {}, + "outputs": [], + "source": [ + "# Теперь отсортируем значения с помощью метода .values().\n", + "sorted(dict_to_sort.values())" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "e38d9c47", + "metadata": {}, + "outputs": [], + "source": [ + "# Если мы хотим отсортировать пары «ключ : значение» по ключу\n", + "# или по значению, вначале воспользуемся методом .items() для\n", + "# извлечения этих пар (кортежей) из словаря.\n", + "dict_to_sort.items()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "276bf028", + "metadata": {}, + "outputs": [], + "source": [ + "# для их сортировки по ключу (индекс [0]) воспользуемся методом\n", + "# .items() и lambda-функцией\n", + "sorted(dict_to_sort.items(), key=lambda item: item[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "42304d88", + "metadata": {}, + "outputs": [], + "source": [ + "# сортировка по значению выполняется так же, однако\n", + "# lambda-функции мы передаем индекс [1]\n", + "sorted(dict_to_sort.items(), key=lambda item: item[1])" + ] + }, + { + "cell_type": "markdown", + "id": "c14f0481", + "metadata": {}, + "source": [ + "# Копирование словарей" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "bada8ce0", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим исходный словарь с количеством студентов на курсах\n", + "students_per_course: dict[str, int] = {\n", + " \"Course 1\": 120,\n", + " \"Course 2\": 95,\n", + " \"Course 3\": 150,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "89534c6b", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим копию исходного словаря с помощью метода .copy()\n", + "students_per_course_copy: dict[str, int] = students_per_course.copy()\n", + "students_per_course_copy" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "2d14f8db", + "metadata": {}, + "outputs": [], + "source": [ + "# добавим информацию о четвертом курсе в новый словарь\n", + "students_per_course_copy[\"Course 4\"] = 80\n", + "students_per_course_copy" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "b7f7c57b", + "metadata": {}, + "outputs": [], + "source": [ + "# выведем исходный и новый словари\n", + "print(f\"Original dictionary: {students_per_course}\")\n", + "print(f\"New dictionary: {students_per_course_copy}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "fec975d6", + "metadata": {}, + "outputs": [], + "source": [ + "# Копирование через оператор присваивания = (так делать не стоит!)\n", + "# передадим исходный словарь в новую переменную\n", + "students_per_course_assigned: dict[str, int] = students_per_course\n", + "\n", + "# удалим элементы нового словаря\n", + "students_per_course_assigned.clear()\n", + "\n", + "# выведем исходный и новый словари\n", + "print(f\"Original dictionary after clear: {students_per_course}\")\n", + "print(f\"Assigned dictionary after clear: {students_per_course_assigned}\")" + ] + }, + { + "cell_type": "markdown", + "id": "0f63e418", + "metadata": {}, + "source": [ + "# Функция dir()" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "f519fcbc", + "metadata": {}, + "outputs": [], + "source": [ + "# функция dir() возвращает все методы передаваемого ей объекта\n", + "some_dict: dict[str, int] = {\"a\": 1, \"b\": 2}\n", + "dir(some_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "7697969c", + "metadata": {}, + "outputs": [], + "source": [ + "# Вначале всегда идут так называемые специальные методы.\n", + "# Они начинаются и заканчиваются символом двойного подчеркивания\n", + "# __.\n", + "\n", + "# Например, когда мы вызываем функцию print() и передаем ей\n", + "# словарь.\n", + "print(some_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "6d6332e5", + "metadata": {}, + "outputs": [], + "source": [ + "# На самом деле мы применяем к словарю метод __str__().\n", + "str(some_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "566751bc", + "metadata": {}, + "outputs": [], + "source": [ + "# В большинстве случаев нас будут интересовать методы без\n", + "# символов двойного подчеркивания. Выведем последние 11\n", + "# элементов списка\n", + "dict_methods: list[str] = dir(some_dict)[-11:]\n", + "dict_methods" + ] + }, + { + "cell_type": "markdown", + "id": "6a457fd4", + "metadata": {}, + "source": [ + "# Dict comprehension" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "a799ff45", + "metadata": {}, + "outputs": [], + "source": [ + "# Dictionary comprehension, как и в случае со списками,\n", + "# позволяет превратить один словарь в другой. В процессе этого\n", + "# превращения, элементы исходного словаря могут быть изменены\n", + "# или отобраны на основе какого-либо условия.\n", + "\n", + "# создадим еще один словарь\n", + "source_dict: dict[str, int] = {\"k1\": 2, \"k2\": 4, \"k3\": 6}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a67e66d", + "metadata": {}, + "outputs": [], + "source": [ + "# В первом примере умножим каждое значение на два.\n", + "new_dict: dict[str, int] = {key: value * 2 for key, value in source_dict.items()}\n", + "new_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "ab5ae331", + "metadata": {}, + "outputs": [], + "source": [ + "# Во втором примере сделаем символы всех ключей заглавными\n", + "# с помощью метода .upper().\n", + "new_dict_upper: dict[str, int] = {\n", + " key.upper(): value for key, value in source_dict.items()\n", + "}\n", + "new_dict_upper" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77e13401", + "metadata": {}, + "outputs": [], + "source": [ + "# Теперь предположим, мы хотим отсортировать те пары, в которых\n", + "# значение больше двух, но меньше шести.\n", + "filtered_dict: dict[str, int] = {\n", + " key: value for key, value in source_dict.items() if 2 < value < 6\n", + "}\n", + "filtered_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4f6b86a", + "metadata": {}, + "outputs": [], + "source": [ + "# Если бы мы выполняли эту задачу с помощью цикла for, то\n", + "# использовали бы оператор И.\n", + "new_dict_loop: dict[str, int] = {}\n", + "\n", + "for key, value in source_dict.items():\n", + " if 2 < value < 6:\n", + " # если условия верны, записываем ключ и значение в новый\n", + " # словарь\n", + " new_dict_loop[key] = value\n", + "\n", + "new_dict_loop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c694b80", + "metadata": {}, + "outputs": [], + "source": [ + "# условие с if-else ставится в самом начале схемы dict\n", + "# comprehension заменим значение на слово even, если оно четное,\n", + "# и odd, если нечетное\n", + "filtered_dict_parity: dict[str, str] = {\n", + " key: (\"even\" if value % 2 == 0 else \"odd\") for key, value in source_dict.items()\n", + "}\n", + "filtered_dict_parity" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "f8449747", + "metadata": {}, + "outputs": [], + "source": [ + "# Dict comprehension можно использовать вместо метода .fromkeys().\n", + "# создадим кортеж из ключей\n", + "keys2: tuple[str, ...] = (\"k1\", \"k2\", \"k3\")" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "a5ea066c", + "metadata": {}, + "outputs": [], + "source": [ + "# передадим словарю ключи из кортежа keys2 и зададим значение 0\n", + "# каждому из них\n", + "new_dict_fromkeys: dict[str, int] = {key: 0 for key in keys2}\n", + "new_dict_fromkeys" + ] + }, + { + "cell_type": "markdown", + "id": "11e92d73", + "metadata": {}, + "source": [ + "# lambda-функция, функции map() и zip()" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "3a413e09", + "metadata": {}, + "outputs": [], + "source": [ + "# Возьмем список фруктов.\n", + "fruits: list[str] = [\"apple\", \"banana\", \"cherry\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "972dbe08", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим lambda-функцию, которая посчитает длину передаваемого\n", + "# ей слова с помощью функции map() применим lambda-функцию к\n", + "# каждому элементу списка words и поместим длины слов в новый\n", + "# список length с помощью функции list()\n", + "length: list[int] = list(map(len, fruits))\n", + "length" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "0f6fc491", + "metadata": {}, + "outputs": [], + "source": [ + "# с помощью функции zip() поэлементно соединим оба списка и\n", + "# преобразуем в словарь\n", + "fruit_length_dict: dict[str, int] = dict(zip(fruits, length))\n", + "fruit_length_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6853cc9", + "metadata": {}, + "outputs": [], + "source": [ + "# то же самое можно сделать с помощью функции zip() и\n", + "# list comprehension\n", + "fruit_length_dict_comp: dict[str, int] = {fruit: len(fruit) for fruit in fruits}\n", + "fruit_length_dict_comp" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "58acf97c", + "metadata": {}, + "outputs": [], + "source": [ + "# Пример со словарем\n", + "# Теперь поработаем со словарями. Предположим, что мы продолжаем\n", + "# спрашивать людей об их росте, и у нас есть несколько\n", + "# американцев, которые сообщили свой рост в футах.\n", + "height_feet: dict[str, float] = {\"Alex\": 6.1, \"Jerry\": 5.4, \"Ben\": 5.8}" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "ecc4918c", + "metadata": {}, + "outputs": [], + "source": [ + "# Наша задача — создать точно такой же словарь, но чтобы футы\n", + "# были преобразованы в метры. Вначале создадим список с данными\n", + "# о росте в метрах. Один фут равен 0,3048 метра\n", + "height_meters: list[float] = list(\n", + " map(lambda height: height * 0.3048, height_feet.values())\n", + ")\n", + "height_meters" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "928d7e43", + "metadata": {}, + "outputs": [], + "source": [ + "# с помощью функции zip() соединим ключи исходного словаря с\n", + "# элементами списка metres\n", + "height_meters_rounded: dict[str, object] = dict(\n", + " zip(height_feet.keys(), np.round(height_meters, 2))\n", + ")\n", + "height_meters_rounded" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "118afd58", + "metadata": {}, + "outputs": [], + "source": [ + "# Как и в предыдущем примере, эту задачу можно решить с помощью\n", + "# dict comprehension.\n", + "height_meters_comp: dict[str, object] = {\n", + " key: np.round(value * 0.3048, 2) for key, value in height_feet.items()\n", + "}\n", + "height_meters_comp" + ] + }, + { + "cell_type": "markdown", + "id": "8d5038a3", + "metadata": {}, + "source": [ + "# Вложенные словари" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66a28ae2", + "metadata": {}, + "outputs": [], + "source": [ + "# возьмем словарь, ключами которого будут id сотрудников\n", + "employees: dict[str, dict[str, str | int | float]] = {\n", + " \"id1\": {\n", + " \"first name\": \"Александр\",\n", + " \"last name\": \"Иванов\",\n", + " \"age\": 30,\n", + " \"job\": \"программист\",\n", + " },\n", + " \"id2\": {\n", + " \"first name\": \"Ольга\",\n", + " \"last name\": \"Петрова\",\n", + " \"age\": 35,\n", + " \"job\": \"ML-engineer\",\n", + " },\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "df732895", + "metadata": {}, + "outputs": [], + "source": [ + "# В данном случае ключами словаря выступают id сотрудников, а\n", + "# значениями — вложенные словари с информацией о них.\n", + "for emp_id, info in employees.items():\n", + " print(f\"ID: {emp_id}, Info: {info}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "e57f5607", + "metadata": {}, + "outputs": [], + "source": [ + "# Базовые операции\n", + "# первый ключ - нужный нам сотрудник, второй - элемент с\n", + "# информацией о нем\n", + "employees[\"id1\"][\"first name\"] # Александр" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "1f9ffa24", + "metadata": {}, + "outputs": [], + "source": [ + "# Функция pprint() расшифровывается как pretty print («красивая\n", + "# печать») и в некоторых случаях справляется со своей задачей\n", + "# лучше обычной функции print()." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "caac42e2", + "metadata": {}, + "outputs": [], + "source": [ + "# добавим информацию о новом сотруднике\n", + "employees[\"id3\"] = {\n", + " \"first name\": \"Дарья\",\n", + " \"last name\": \"Некрасова\",\n", + " \"age\": 27,\n", + " \"job\": \"веб-дизайнер\",\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "9bf782a3", + "metadata": {}, + "outputs": [], + "source": [ + "# и выведем обновленный словарь с помощью функции pprint()\n", + "pprint(employees)" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "62a39230", + "metadata": {}, + "outputs": [], + "source": [ + "# Циклы for\n", + "# Посмотрим, как можно использовать цикл for со вложенными\n", + "# словарями. Давайте заменим тип данных с информацией о возрасте\n", + "# сотрудника с int на float.\n", + "\n", + "# для этого вначале пройдемся по вложенным словарям, т.е. по\n", + "# значениям info внешнего словаря employees затем по ключам и\n", + "# значениям вложенного словаря info если ключ совпадет со словом\n", + "# 'age' преобразуем значение в тип float\n", + "for info in employees.values():\n", + " for emp_key, emp_value in info.items():\n", + " if emp_key == \"age\" and isinstance(emp_value, int):\n", + " info[emp_key] = float(emp_value)\n", + "\n", + "pprint(employees)" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "e054d715", + "metadata": {}, + "outputs": [], + "source": [ + "# Вложенные словари и dict comprehension\n", + "# преобразуем обратно из float в int, но уже через dict\n", + "# comprehension для начала просто выведем словарь employees без\n", + "# изменений\n", + "pprint({emp_id: info for emp_id, info in employees.items()})" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "42180514", + "metadata": {}, + "outputs": [], + "source": [ + "# а затем заменим значение внешнего словаря info (т.е.\n", + "# вложенный словарь) на еще один dict comprehension с условием\n", + "# if-else\n", + "pprint(\n", + " {\n", + " emp_id: {\n", + " emp_key: (\n", + " int(emp_value)\n", + " if emp_key == \"age\" and isinstance(emp_value, float)\n", + " else emp_value\n", + " )\n", + " for emp_key, emp_value in info.items()\n", + " }\n", + " for emp_id, info in employees.items()\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "239b056b", + "metadata": {}, + "source": [ + "# Частота слов в тексте" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "c229dd24", + "metadata": {}, + "outputs": [], + "source": [ + "# Напоследок разберем очень несложный пример подсчета частоты\n", + "# слов в тексте. Это уже знакомый нам мешок слов (Bag of Words,\n", + "# BoW). В качестве примера возьмем уже известный нам текст про\n", + "# Париж, музеи и искусство.\n", + "corpus: str = (\n", + " \"When we were in Paris we visited a lot of museums. We first \"\n", + " \"went to the Louvre, the largest art museum in the world. I \"\n", + " \"have always been interested in art so I spent many hours \"\n", + " \"there. The museum is enormous, so a week there would not \"\n", + " \"be enough.\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "faad139a", + "metadata": {}, + "outputs": [], + "source": [ + "# Предварительная обработка текста\n", + "# Превратим строку в список слов.\n", + "words: list[str] = corpus.split()\n", + "print(words)" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "d4ce94e1", + "metadata": {}, + "outputs": [], + "source": [ + "# Применим list comprehension, чтобы избавиться от точек и\n", + "# запятых. Помимо этого, переведем все слова в нижний регистр.\n", + "words = [word.strip(\".,\").lower() for word in words]\n", + "print(words)" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "11b670a0", + "metadata": {}, + "outputs": [], + "source": [ + "# Мы готовы создавать мешки слов разными способами.\n", + "# Способ 1. Условие if-else\n", + "# создадим пустой словарь для мешка слов bow\n", + "bow_1: dict[str, int] = {}\n", + "\n", + "# пройдемся по словам текста\n", + "for word in words:\n", + " # если нам встретилось слово, которое уже есть в словаре\n", + " if word in bow_1:\n", + " # увеличим его значение (частоту) на 1\n", + " bow_1[word] = bow_1[word] + 1\n", + " # в противном случае, если слово встречается впервые\n", + " else:\n", + " # зададим ему значение 1\n", + " bow_1[word] = 1\n", + "\n", + "# отсортируем словарь по значению в убывающем порядке\n", + "# (reverse = True) и выведем шесть наиболее частотных слов\n", + "top_words_1: list[tuple[str, int]] = sorted(\n", + " bow_1.items(), key=lambda word_item: word_item[1], reverse=True\n", + ")[:6]\n", + "top_words_1" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "a15efce9", + "metadata": {}, + "outputs": [], + "source": [ + "# Способ 2. Метод .get()\n", + "bow_2: dict[str, int] = {}\n", + "\n", + "for word in words:\n", + " bow_2[word] = bow_2.get(word, 0) + 1\n", + "\n", + "top_words_2: list[tuple[str, int]] = sorted(\n", + " bow_2.items(), key=lambda word_item: word_item[1], reverse=True\n", + ")[:6]\n", + "top_words_2" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "95659d97", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим объект этого класса, передав ему список слов\n", + "bow_3: Counter[str] = Counter(words)\n", + "\n", + "# выведем шесть наиболее часто встречающихся слов с помощью\n", + "# метода .most_common()\n", + "bow_3.most_common(6)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_8_dict.py b/python/makarov/chapter_8_dict.py new file mode 100644 index 00000000..0c0b0c42 --- /dev/null +++ b/python/makarov/chapter_8_dict.py @@ -0,0 +1,529 @@ +"""Словарь.""" + +# # Понятие словаря в Питоне + +# + +# пустой словарь можно создать с помощью {} или функции dict() +# Способ 3. Класс Counter модуля collections +from collections import Counter + +# Небольшое отступление от темы. Сложные структуры данных бывает +# удобно вывести с помощью функции pprint() одноименного модуля. +from pprint import pprint + +import numpy as np + +empty_dict1: dict[str, int] = {} +empty_dict2: dict[str, int] = {} +print(empty_dict1) # {} +print(empty_dict2) # {} +# - + +# словарь можно сразу заполнить ключами и значениями +person1: dict[str, str | int] = {"name": "Alice", "age": 30, "city": "New York"} +person1 + +# словарь можно создать из вложенных кортежей +nested_list: list[tuple[str, int]] = [("a", 1), ("b", 2), ("c", 3)] +dict_from_nested_list: dict[str, int] = dict(nested_list) +dict_from_nested_list + +# если поместить ключи в кортеж и задать значение +keys1: tuple[str, ...] = ("name", "age", "city") +value: int = 0 +person2: dict[str, int] = dict.fromkeys(keys1, value) +person2 + +# # Ключи и значения словаря + +# + +# Ключами словаря могут быть только неизменяемые типы данных. +# Например, строки, числа, кортежи или логические значения +# (Boolean). Кроме того, ключи должны быть уникальными и +# соответственно не могут повторяться. + +# Значения словаря, наоборот, могут состоять из чисел, строк, +# пропущенных (NaN) и логических значений, значения типа None, +# списков, массивов Numpy и вложенных словарей. +# - + +# приведем пример того, какими могут быть значения словаря +value_types: dict[str, object] = { + "k1": 123, + "k2": "string", + "k3": np.nan, # тип "Пропущенное значение" + "k4": True, # логическое значение + "k5": None, + "k6": [1, 2, 3], + "k7": np.array([1, 2, 3]), + "k8": {1: "v1", 2: "v2", 3: "v3"}, +} +value_types + +# # Методы .keys(), .values() и .items() + +# создадим несложный словарь с информацией о сотруднике +person: dict[str, str | int] = { + "name": "John Doe", + "age": 28, + "position": "Software Engineer", + "department": "IT", +} +person + +# посмотрим на ключи словаря +person.keys() + +# значения +person.values() + +# а также на пары ключ-значение в виде списка из кортежей +person.items() + +# # Использование цикла for + +# Ключи и значения словаря удобно просматривать с помощью +# цикла for и метода .items(). +for key, person_value in person.items(): + print(f"Key: {key}, Value: {person_value}") + +# Доступ по ключу и метод .get() +# Конкретное значение в словаре можно получить, введя название +# словаря и затем название ключа в квадратных скобках. +person["name"] + +# Если такого ключа нет, Питон выдаст ошибку. +# Для того чтобы этого не произошло, можно использовать метод +# .get(). Он также выводит значение по ключу. +person.get("name") + +# Если ключа в словаре нет, метод .get() возвращает значение +# None. +print(person.get("salary")) # ключа "salary" нет в словаре + +# # Проверка наличия ключа и значения в словаре + +# С помощью оператора in мы можем проверить наличие +# определенного ключа в словаре. +print("age" in person) # True +print("salary" in person) # False + +# Метод .items() поможет проверить наличие пары ключ : значение. +# Обратите внимание, эту пару мы записываем в форме кортежа. +print(("age", 28) in person.items()) # True + +# # Операции со словарями + +# Добавление и изменение элементов +# Добавить элемент можно, передав новому ключу новое значение. +person["salary"] = 70000 +person + +# Изменить элемент можно передав существующему ключу новое +# значение. +person["age"] = 29 +person + +# Метод .update() позволяет соединить два словаря. +additional_info: dict[str, str] = {"email": "john.doe@example.com"} + +# и присоединим его к существующему словарю с помощью метода +# .update() +person.update(additional_info) + +person + +# Метод .setdefault() не изменяет значение, если указанный ключ +# уже содержится в словаре. +person.setdefault("age", 35) # ключ "age" уже есть в словаре +person + +# Если такого ключа нет, ключ и соответствующее значение будут +# добавлены в словарь. +person.setdefault("phone", "123-456-7890") +person + +# Удаление элементов +# Метод .pop() удаляет элемент по ключу и выводит удаляемое +# значение. +removed_value: str | int = person.pop("salary") +print(f"Removed value: {removed_value}") + +# Убедимся, что этой пары ключа и значения больше нет в +# словаре. +person + +# Ключевое слово del также удаляет элемент по ключу. +del person["phone"] +person + +# Метод .popitem() удаляет и выводит последний добавленный в +# словарь элемент. +removed_item: tuple[str, str | int] = person.popitem() +print(f"Removed item: {removed_item}") + +# Метод .clear() удаляет все ключи и значения и возвращает +# пустой словарь. +person.clear() +person + +# + +# удалим весь словарь +del person + +# если попытаться вновь вызвать эту переменную, Питон выдаст +# ошибку +# - + +# # Сортировка словарей + +# Для сортировки словарей можно использовать функцию sorted(). +# возьмем несложный словарь +dict_to_sort: dict[str, int] = { + "banana": 3, + "apple": 5, + "orange": 2, +} + +# Отсортируем ключи этого словаря. +sorted(dict_to_sort) + +# Теперь отсортируем значения с помощью метода .values(). +sorted(dict_to_sort.values()) + +# Если мы хотим отсортировать пары «ключ : значение» по ключу +# или по значению, вначале воспользуемся методом .items() для +# извлечения этих пар (кортежей) из словаря. +dict_to_sort.items() + +# для их сортировки по ключу (индекс [0]) воспользуемся методом +# .items() и lambda-функцией +sorted(dict_to_sort.items(), key=lambda item: item[0]) + +# сортировка по значению выполняется так же, однако +# lambda-функции мы передаем индекс [1] +sorted(dict_to_sort.items(), key=lambda item: item[1]) + +# # Копирование словарей + +# создадим исходный словарь с количеством студентов на курсах +students_per_course: dict[str, int] = { + "Course 1": 120, + "Course 2": 95, + "Course 3": 150, +} + +# создадим копию исходного словаря с помощью метода .copy() +students_per_course_copy: dict[str, int] = students_per_course.copy() +students_per_course_copy + +# добавим информацию о четвертом курсе в новый словарь +students_per_course_copy["Course 4"] = 80 +students_per_course_copy + +# выведем исходный и новый словари +print(f"Original dictionary: {students_per_course}") +print(f"New dictionary: {students_per_course_copy}") + +# + +# Копирование через оператор присваивания = (так делать не стоит!) +# передадим исходный словарь в новую переменную +students_per_course_assigned: dict[str, int] = students_per_course + +# удалим элементы нового словаря +students_per_course_assigned.clear() + +# выведем исходный и новый словари +print(f"Original dictionary after clear: {students_per_course}") +print(f"Assigned dictionary after clear: {students_per_course_assigned}") +# - + +# # Функция dir() + +# функция dir() возвращает все методы передаваемого ей объекта +some_dict: dict[str, int] = {"a": 1, "b": 2} +dir(some_dict) + +# + +# Вначале всегда идут так называемые специальные методы. +# Они начинаются и заканчиваются символом двойного подчеркивания +# __. + +# Например, когда мы вызываем функцию print() и передаем ей +# словарь. +print(some_dict) +# - + +# На самом деле мы применяем к словарю метод __str__(). +str(some_dict) + +# В большинстве случаев нас будут интересовать методы без +# символов двойного подчеркивания. Выведем последние 11 +# элементов списка +dict_methods: list[str] = dir(some_dict)[-11:] +dict_methods + +# # Dict comprehension + +# + +# Dictionary comprehension, как и в случае со списками, +# позволяет превратить один словарь в другой. В процессе этого +# превращения, элементы исходного словаря могут быть изменены +# или отобраны на основе какого-либо условия. + +# создадим еще один словарь +source_dict: dict[str, int] = {"k1": 2, "k2": 4, "k3": 6} +# - + +# В первом примере умножим каждое значение на два. +new_dict: dict[str, int] = {key: value * 2 for key, value in source_dict.items()} +new_dict + +# Во втором примере сделаем символы всех ключей заглавными +# с помощью метода .upper(). +new_dict_upper: dict[str, int] = { + key.upper(): value for key, value in source_dict.items() +} +new_dict_upper + +# Теперь предположим, мы хотим отсортировать те пары, в которых +# значение больше двух, но меньше шести. +filtered_dict: dict[str, int] = { + key: value for key, value in source_dict.items() if 2 < value < 6 +} +filtered_dict + +# + +# Если бы мы выполняли эту задачу с помощью цикла for, то +# использовали бы оператор И. +new_dict_loop: dict[str, int] = {} + +for key, value in source_dict.items(): + if 2 < value < 6: + # если условия верны, записываем ключ и значение в новый + # словарь + new_dict_loop[key] = value + +new_dict_loop +# - + +# условие с if-else ставится в самом начале схемы dict +# comprehension заменим значение на слово even, если оно четное, +# и odd, если нечетное +filtered_dict_parity: dict[str, str] = { + key: ("even" if value % 2 == 0 else "odd") for key, value in source_dict.items() +} +filtered_dict_parity + +# Dict comprehension можно использовать вместо метода .fromkeys(). +# создадим кортеж из ключей +keys2: tuple[str, ...] = ("k1", "k2", "k3") + +# передадим словарю ключи из кортежа keys2 и зададим значение 0 +# каждому из них +new_dict_fromkeys: dict[str, int] = {key: 0 for key in keys2} +new_dict_fromkeys + +# # lambda-функция, функции map() и zip() + +# Возьмем список фруктов. +fruits: list[str] = ["apple", "banana", "cherry"] + +# создадим lambda-функцию, которая посчитает длину передаваемого +# ей слова с помощью функции map() применим lambda-функцию к +# каждому элементу списка words и поместим длины слов в новый +# список length с помощью функции list() +length: list[int] = list(map(len, fruits)) +length + +# с помощью функции zip() поэлементно соединим оба списка и +# преобразуем в словарь +fruit_length_dict: dict[str, int] = dict(zip(fruits, length)) +fruit_length_dict + +# то же самое можно сделать с помощью функции zip() и +# list comprehension +fruit_length_dict_comp: dict[str, int] = {fruit: len(fruit) for fruit in fruits} +fruit_length_dict_comp + +# Пример со словарем +# Теперь поработаем со словарями. Предположим, что мы продолжаем +# спрашивать людей об их росте, и у нас есть несколько +# американцев, которые сообщили свой рост в футах. +height_feet: dict[str, float] = {"Alex": 6.1, "Jerry": 5.4, "Ben": 5.8} + +# Наша задача — создать точно такой же словарь, но чтобы футы +# были преобразованы в метры. Вначале создадим список с данными +# о росте в метрах. Один фут равен 0,3048 метра +height_meters: list[float] = list( + map(lambda height: height * 0.3048, height_feet.values()) +) +height_meters + +# с помощью функции zip() соединим ключи исходного словаря с +# элементами списка metres +height_meters_rounded: dict[str, object] = dict( + zip(height_feet.keys(), np.round(height_meters, 2)) +) +height_meters_rounded + +# Как и в предыдущем примере, эту задачу можно решить с помощью +# dict comprehension. +height_meters_comp: dict[str, object] = { + key: np.round(value * 0.3048, 2) for key, value in height_feet.items() +} +height_meters_comp + +# # Вложенные словари + +# возьмем словарь, ключами которого будут id сотрудников +employees: dict[str, dict[str, str | int | float]] = { + "id1": { + "first name": "Александр", + "last name": "Иванов", + "age": 30, + "job": "программист", + }, + "id2": { + "first name": "Ольга", + "last name": "Петрова", + "age": 35, + "job": "ML-engineer", + }, +} + +# В данном случае ключами словаря выступают id сотрудников, а +# значениями — вложенные словари с информацией о них. +for emp_id, info in employees.items(): + print(f"ID: {emp_id}, Info: {info}") + +# Базовые операции +# первый ключ - нужный нам сотрудник, второй - элемент с +# информацией о нем +employees["id1"]["first name"] # Александр + +# + +# Функция pprint() расшифровывается как pretty print («красивая +# печать») и в некоторых случаях справляется со своей задачей +# лучше обычной функции print(). +# - + +# добавим информацию о новом сотруднике +employees["id3"] = { + "first name": "Дарья", + "last name": "Некрасова", + "age": 27, + "job": "веб-дизайнер", +} + +# и выведем обновленный словарь с помощью функции pprint() +pprint(employees) + +# + +# Циклы for +# Посмотрим, как можно использовать цикл for со вложенными +# словарями. Давайте заменим тип данных с информацией о возрасте +# сотрудника с int на float. + +# для этого вначале пройдемся по вложенным словарям, т.е. по +# значениям info внешнего словаря employees затем по ключам и +# значениям вложенного словаря info если ключ совпадет со словом +# 'age' преобразуем значение в тип float +for info in employees.values(): + for emp_key, emp_value in info.items(): + if emp_key == "age" and isinstance(emp_value, int): + info[emp_key] = float(emp_value) + +pprint(employees) +# - + +# Вложенные словари и dict comprehension +# преобразуем обратно из float в int, но уже через dict +# comprehension для начала просто выведем словарь employees без +# изменений +pprint({emp_id: info for emp_id, info in employees.items()}) + +# а затем заменим значение внешнего словаря info (т.е. +# вложенный словарь) на еще один dict comprehension с условием +# if-else +pprint( + { + emp_id: { + emp_key: ( + int(emp_value) + if emp_key == "age" and isinstance(emp_value, float) + else emp_value + ) + for emp_key, emp_value in info.items() + } + for emp_id, info in employees.items() + } +) + +# # Частота слов в тексте + +# Напоследок разберем очень несложный пример подсчета частоты +# слов в тексте. Это уже знакомый нам мешок слов (Bag of Words, +# BoW). В качестве примера возьмем уже известный нам текст про +# Париж, музеи и искусство. +corpus: str = ( + "When we were in Paris we visited a lot of museums. We first " + "went to the Louvre, the largest art museum in the world. I " + "have always been interested in art so I spent many hours " + "there. The museum is enormous, so a week there would not " + "be enough." +) + +# Предварительная обработка текста +# Превратим строку в список слов. +words: list[str] = corpus.split() +print(words) + +# Применим list comprehension, чтобы избавиться от точек и +# запятых. Помимо этого, переведем все слова в нижний регистр. +words = [word.strip(".,").lower() for word in words] +print(words) + +# + +# Мы готовы создавать мешки слов разными способами. +# Способ 1. Условие if-else +# создадим пустой словарь для мешка слов bow +bow_1: dict[str, int] = {} + +# пройдемся по словам текста +for word in words: + # если нам встретилось слово, которое уже есть в словаре + if word in bow_1: + # увеличим его значение (частоту) на 1 + bow_1[word] = bow_1[word] + 1 + # в противном случае, если слово встречается впервые + else: + # зададим ему значение 1 + bow_1[word] = 1 + +# отсортируем словарь по значению в убывающем порядке +# (reverse = True) и выведем шесть наиболее частотных слов +top_words_1: list[tuple[str, int]] = sorted( + bow_1.items(), key=lambda word_item: word_item[1], reverse=True +)[:6] +top_words_1 + +# + +# Способ 2. Метод .get() +bow_2: dict[str, int] = {} + +for word in words: + bow_2[word] = bow_2.get(word, 0) + 1 + +top_words_2: list[tuple[str, int]] = sorted( + bow_2.items(), key=lambda word_item: word_item[1], reverse=True +)[:6] +top_words_2 + +# + +# создадим объект этого класса, передав ему список слов +bow_3: Counter[str] = Counter(words) + +# выведем шесть наиболее часто встречающихся слов с помощью +# метода .most_common() +bow_3.most_common(6) diff --git a/python/makarov/chapter_9_oop.ipynb b/python/makarov/chapter_9_oop.ipynb new file mode 100644 index 00000000..53637f1f --- /dev/null +++ b/python/makarov/chapter_9_oop.ipynb @@ -0,0 +1,1195 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "d0e9411a", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"ООП. Классы и объекты.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "186d0626", + "metadata": {}, + "source": [ + "# Классы и объекты в Питоне" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e75fe392", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим класс CatClass\n", + "import numpy as np\n", + "\n", + "\n", + "class CatClass:\n", + " \"\"\"Простой класс для демонстрации основ ООП.\"\"\"\n", + "\n", + " def __init__(self) -> None:\n", + " \"\"\"Инициализация объекта класса CatClass.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "62d87976", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание объекта\n", + "\n", + "# создадим объект Matroskin класса CatClass\n", + "Matroskin: CatClass = CatClass()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "81e8ab61", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.CatClass" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# проверим тип данных созданной переменной\n", + "type(Matroskin)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6525c737", + "metadata": {}, + "outputs": [], + "source": [ + "# Атрибуты класса\n", + "# Давайте дополним класс CatClass атрибутом типа (type_)\n", + "# и атрибутом цвета шерсти (color).\n", + "\n", + "\n", + "# вновь создадим класс CatClass\n", + "class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined\n", + " \"\"\"Класс для представления кошки с атрибутами.\"\"\"\n", + "\n", + " def __init__(self, color: str) -> None:\n", + " \"\"\"Инициализация объекта CatClass с цветом.\n", + "\n", + " Args:\n", + " color: Цвет шерсти кошки.\n", + " \"\"\"\n", + " # этот параметр будет записан в переменную атрибута\n", + " self.color: str = color\n", + "\n", + " # значение атрибута type_ задается внутри класса\n", + " self.type_: str = \"cat\"" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e115491e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('gray', 'cat')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# повторно создадим объект класса CatClass\n", + "# передав ему параметр цвета шерсти\n", + "Matroskin = CatClass(\"gray\") # type: ignore[call-arg]\n", + "\n", + "# и выведем атрибуты класса\n", + "Matroskin.color, Matroskin.type_ # type: ignore[attr-defined]" + ] + }, + { + "cell_type": "markdown", + "id": "7ffb308f", + "metadata": {}, + "source": [ + "# Методы класса" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a296eb70", + "metadata": {}, + "outputs": [], + "source": [ + "# Дополним класс возможностью выполнять определенные действия\n", + "\n", + "\n", + "# перепишем класс CatClass\n", + "class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined\n", + " \"\"\"Класс кошки с методами.\"\"\"\n", + "\n", + " def __init__(self, color: str) -> None:\n", + " \"\"\"Инициализация кошки.\n", + "\n", + " Args:\n", + " color: Цвет шерсти кошки.\n", + " \"\"\"\n", + " self.color: str = color\n", + " self.type_: str = \"cat\"\n", + "\n", + " def meow(self) -> None:\n", + " \"\"\"Кошка мяукает три раза.\"\"\"\n", + " for _ in range(3):\n", + " print(\"Мяу\")\n", + "\n", + " def info(self) -> None:\n", + " \"\"\"Выводит информацию об объекте.\"\"\"\n", + " print(self.color, self.type_)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "953df6f5", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим объект\n", + "Matroskin = CatClass(\"gray\") # type: ignore[call-arg]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "87d5d7c8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Мяу\n", + "Мяу\n", + "Мяу\n" + ] + } + ], + "source": [ + "# применим метод .meow()\n", + "Matroskin.meow() # type: ignore[attr-defined]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "67c689c0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gray cat\n" + ] + } + ], + "source": [ + "# и метод .info()\n", + "Matroskin.info() # type: ignore[attr-defined]" + ] + }, + { + "cell_type": "markdown", + "id": "536581cd", + "metadata": {}, + "source": [ + "# Принципы объектно-ориентированного программирования" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba112a71", + "metadata": {}, + "outputs": [], + "source": [ + "# Инкапсуляция\n", + "\n", + "# Инкапсуляция (encapsulation) — это способность класса\n", + "# хранить данные и методы внутри себя." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a893a675", + "metadata": {}, + "outputs": [], + "source": [ + "# Публичные и частные атрибуты класса\n", + "\n", + "# Публичные атрибуты — это те атрибуты, к которым можно получить\n", + "# доступ за пределами «капсулы» класса." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "23a75346", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'dog'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Причем не просто получить доступ, но и изменить их.\n", + "\n", + "# изменим атрибут type_ объекта Matroskin на dog\n", + "Matroskin.type_ = \"dog\" # type: ignore[attr-defined]\n", + "\n", + "# выведем этот атрибут\n", + "Matroskin.type_ # type: ignore[attr-defined]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ae8ade31", + "metadata": {}, + "outputs": [], + "source": [ + "# Способ 1. Один символ подчеркивания указывает на приватный атрибут\n", + "\n", + "\n", + "class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined\n", + " \"\"\"Класс кошки с защищенным атрибутом.\"\"\"\n", + "\n", + " def __init__(self, color: str) -> None:\n", + " \"\"\"Инициализация кошки.\n", + "\n", + " Args:\n", + " color: Цвет шерсти кошки.\n", + " \"\"\"\n", + " self.color: str = color\n", + " # символ подчеркивания указывает на защищенный атрибут\n", + " self._type_: str = \"cat\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88b89c80", + "metadata": {}, + "outputs": [], + "source": [ + "# Способ 2. Двойное подчеркивание перед названием атрибута\n", + "\n", + "\n", + "class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined\n", + " \"\"\"Класс кошки с приватным атрибутом.\"\"\"\n", + "\n", + " def __init__(self, color: str) -> None:\n", + " \"\"\"Инициализация кошки.\n", + "\n", + " Args:\n", + " color: Цвет шерсти кошки.\n", + " \"\"\"\n", + " self.color: str = color\n", + " # символ двойного подчеркивания предотвратит доступ извне\n", + " self.__type_: str = \"cat\" # pylint: disable=unused-private-member" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1a7ff7a0", + "metadata": {}, + "outputs": [], + "source": [ + "Matroskin = CatClass(\"gray\") # type: ignore[call-arg]\n", + "\n", + "# теперь при вызове этого атрибута Питон выдаст ошибку\n", + "# Matroskin.__type_" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f568b9f", + "metadata": {}, + "outputs": [], + "source": [ + "# Наследование\n", + "# Наследование — это способность класса наследовать атрибуты\n", + "# и методы другого класса." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "a0f0f6c4", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим класс Animal\n", + "\n", + "\n", + "class Animal:\n", + " \"\"\"Базовый класс для животных.\"\"\"\n", + "\n", + " def __init__(self, weight: float, length: float) -> None:\n", + " \"\"\"Инициализация животного.\n", + "\n", + " Args:\n", + " weight: Вес животного в килограммах.\n", + " length: Длина животного в сантиметрах.\n", + " \"\"\"\n", + " self.weight: float = weight\n", + " self.length: float = length\n", + "\n", + " def eat(self) -> None:\n", + " \"\"\"Животное ест.\"\"\"\n", + " print(\"Eating\")\n", + "\n", + " def sleep(self) -> None:\n", + " \"\"\"Животное спит.\"\"\"\n", + " print(\"Sleeping\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c2e34daa", + "metadata": {}, + "outputs": [], + "source": [ + "# Создадим класс-потомок Bird (птица)\n", + "\n", + "\n", + "# создадим класс Bird, наследующий от Animal\n", + "class Bird(Animal):\n", + " \"\"\"Класс для представления птиц.\"\"\"\n", + "\n", + " def move(self) -> None:\n", + " \"\"\"Птица летает.\"\"\"\n", + " print(\"Flying\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d47dd14", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим объект pigeon и передадим ему значения веса и длины\n", + "pigeon: Bird = Bird(0.3, 30)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "3ef250ea", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.3, 30)" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# посмотрим на унаследованные у класса Animal атрибуты\n", + "pigeon.weight, pigeon.length" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "137a2fda", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Eating\n" + ] + } + ], + "source": [ + "# и методы\n", + "pigeon.eat()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "58d684f7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Flying\n" + ] + } + ], + "source": [ + "# Кроме того, мы можем вызвать метод, свойственный только классу Bird\n", + "\n", + "pigeon.move()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aabb6f04", + "metadata": {}, + "outputs": [], + "source": [ + "# Функция super()\n", + "# Предположим, что в наш класс Bird мы хотим добавить\n", + "# атрибут flying_speed (скорость полета).\n", + "\n", + "\n", + "# снова создадим класс Bird\n", + "class Bird(Animal): # type: ignore[no-redef] # pylint: disable=function-redefined\n", + " \"\"\"Класс птицы со скоростью полета.\"\"\"\n", + "\n", + " def __init__(self, weight: float, length: float, flying_speed: float) -> None:\n", + " \"\"\"Инициализация птицы.\n", + "\n", + " Args:\n", + " weight: Вес птицы в килограммах.\n", + " length: Длина птицы в сантиметрах.\n", + " flying_speed: Скорость полета в км/ч.\n", + " \"\"\"\n", + " # вызовем метод родительского класса Animal\n", + " super().__init__(weight, length)\n", + " self.flying_speed: float = flying_speed\n", + "\n", + " def move(self) -> None:\n", + " \"\"\"Птица летает.\"\"\"\n", + " print(\"Flying\")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "7713420f", + "metadata": {}, + "outputs": [], + "source": [ + "# Без функции super() класс Bird не знал бы откуда брать\n", + "# параметры weight и length.\n", + "\n", + "\n", + "# вновь создадим объект pigeon класса Bird с тремя параметрами\n", + "pigeon = Bird(0.3, 30, 100) # type: ignore[call-arg]" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "26eee229", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.3, 30, 100)" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# вызовем как унаследованные, так и собственные атрибуты\n", + "pigeon.weight, pigeon.length, pigeon.flying_speed # type: ignore[attr-defined]" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "953addbc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sleeping\n" + ] + } + ], + "source": [ + "# вызовем унаследованный метод .sleep()\n", + "pigeon.sleep()" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "38923f56", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Flying\n" + ] + } + ], + "source": [ + "# и собственный метод .move()\n", + "pigeon.move()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3fb7229d", + "metadata": {}, + "outputs": [], + "source": [ + "# Переопределение класса\n", + "# Переопределение — это способность класса-потомка\n", + "# изменять поведение унаследованных методов.\n", + "\n", + "\n", + "# создадим подкласс Flightless для нелетающих птиц\n", + "class Flightless(Bird): # pylint: disable=super-init-not-called\n", + " \"\"\"Класс для нелетающих птиц.\"\"\"\n", + "\n", + " def __init__(self, running_speed: float) -> None:\n", + " \"\"\"Инициализация нелетающей птицы.\n", + "\n", + " Args:\n", + " running_speed: Скорость бега в км/ч.\n", + " \"\"\"\n", + " # у нас остается только один атрибут\n", + " # pylint: disable=super-init-not-called\n", + " self.running_speed: float = running_speed\n", + "\n", + " def move(self) -> None:\n", + " \"\"\"Нелетающая птица бежит.\"\"\"\n", + " print(\"Running\")" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "dc102b88", + "metadata": {}, + "outputs": [], + "source": [ + "# Создадим объект ostrich (страус) класса Flightless\n", + "ostrich: Flightless = Flightless(60)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "478d1782", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "60" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Посмотрим на значение атрибута скорости\n", + "ostrich.running_speed" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "ced77815", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running\n" + ] + } + ], + "source": [ + "# Теперь посмотрим, переопределился ли метод .move()\n", + "ostrich.move()" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "a13d9c9c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Eating\n" + ] + } + ], + "source": [ + "# Методы всех родительских классов передаются потомкам.\n", + "\n", + "# применим метод .eat() класса Animal\n", + "ostrich.eat()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e3d9414", + "metadata": {}, + "outputs": [], + "source": [ + "# Множественное наследование\n", + "\n", + "# Питон позволяет классу наследовать методы двух и более классов.\n", + "\n", + "# Создадим класс SwimmingBird (водоплавающая птица)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "f8140886", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим родительский класс Fish\n", + "\n", + "\n", + "class Fish:\n", + " \"\"\"Класс для представления рыб.\"\"\"\n", + "\n", + " def swim(self) -> None:\n", + " \"\"\"Рыба плывет.\"\"\"\n", + " print(\"Swimming\")" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "9b69d7e8", + "metadata": {}, + "outputs": [], + "source": [ + "# и еще один родительский класс Bird\n", + "\n", + "\n", + "class Bird: # type: ignore[no-redef] # pylint: disable=function-redefined\n", + " \"\"\"Класс для представления птиц.\"\"\"\n", + "\n", + " def fly(self) -> None:\n", + " \"\"\"Птица летает.\"\"\"\n", + " print(\"Flying\")" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "35a7eb89", + "metadata": {}, + "outputs": [], + "source": [ + "# родительские классы перечисляем в скобках через запятую\n", + "\n", + "\n", + "class SwimmingBird(Fish, Bird):\n", + " \"\"\"Класс для водоплавающих птиц.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "642ae706", + "metadata": {}, + "outputs": [], + "source": [ + "# Создадим объект duck (утка) класса SwimmingBird\n", + "\n", + "duck: SwimmingBird = SwimmingBird() # type: ignore[call-arg]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "726e6312", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Flying\n" + ] + } + ], + "source": [ + "# Утка умеет как летать, так и плавать\n", + "duck.fly() # type: ignore[attr-defined]" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "3f871273", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Swimming\n" + ] + } + ], + "source": [ + "duck.swim()" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "5ad5c6a7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Полиморфизм\n", + "# Полиморфизм — это способность методов с одинаковыми именами\n", + "# вести себя по-разному в разных классах.\n", + "\n", + "# для чисел '+' является оператором сложения\n", + "2 + 2" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "980e951d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'классы и объекты'" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# для строк - оператором объединения\n", + "\"классы\" + \" и \" + \"объекты\"" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "f476775d", + "metadata": {}, + "outputs": [], + "source": [ + "# Полиморфизм функций\n", + "# Полиморфизм функций — это способность одной функции\n", + "# работать с разными типами данных." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "f8942697", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "26" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(\"Программирование на Питоне\")" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "feb89270", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len([\"Программирование\", \"на\", \"Питоне\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "38e4ac48", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len({0: \"Программирование\", 1: \"на\", 2: \"Питоне\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "e60bab79", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(np.array([1, 2, 3]))" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "876067df", + "metadata": {}, + "outputs": [], + "source": [ + "# Полиморфизм классов\n", + "# Полиморфизм классов — это способность методов с одинаковыми\n", + "# именами в разных классах вести себя по-разному." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "b2baf7d7", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим класс котов\n", + "\n", + "\n", + "class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined\n", + " \"\"\"Класс для представления кошек.\"\"\"\n", + "\n", + " def __init__(self, name: str, color: str) -> None:\n", + " \"\"\"Инициализация кошки.\n", + "\n", + " Args:\n", + " name: Имя кошки.\n", + " color: Цвет шерсти кошки.\n", + " \"\"\"\n", + " self.name: str = name\n", + " self._type_: str = \"кот\"\n", + " self.color: str = color\n", + "\n", + " def info(self) -> None:\n", + " \"\"\"Выводит информацию о кошке.\"\"\"\n", + " print(\n", + " f\"Меня зовут {self.name}, я {self._type_}, \"\n", + " f\"цвет моей шерсти {self.color}\"\n", + " )\n", + "\n", + " def sound(self) -> None:\n", + " \"\"\"Издает звук кошки.\"\"\"\n", + " print(\"Я умею мяукать\")" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "c1ebccc3", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим класс собак\n", + "\n", + "\n", + "class DogClass:\n", + " \"\"\"Класс для представления собак.\"\"\"\n", + "\n", + " def __init__(self, name: str, color: str) -> None:\n", + " \"\"\"Инициализация собаки.\n", + "\n", + " Args:\n", + " name: Имя собаки.\n", + " color: Цвет шерсти собаки.\n", + " \"\"\"\n", + " self.name: str = name\n", + " self._type_: str = \"пес\"\n", + " self.color: str = color\n", + "\n", + " def info(self) -> None:\n", + " \"\"\"Выводит информацию о собаке.\"\"\"\n", + " print(\n", + " f\"Меня зовут {self.name}, я {self._type_}, \"\n", + " f\"цвет моей шерсти {self.color}\"\n", + " )\n", + "\n", + " def sound(self) -> None:\n", + " \"\"\"Издает звук собаки.\"\"\"\n", + " print(\"Я умею лаять\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d160fc34", + "metadata": {}, + "outputs": [], + "source": [ + "# Создадим объекты этих классов\n", + "cat: CatClass = CatClass(\"Бегемот\", \"черный\") # type: ignore[call-arg]\n", + "dog: DogClass = DogClass(\"Барбос\", \"серый\")" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "57e37ccb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Меня зовут Бегемот, я кот, цвет моей шерсти черный\n", + "Я умею мяукать\n", + "\n", + "Меня зовут Барбос, я пес, цвет моей шерсти серый\n", + "Я умею лаять\n", + "\n" + ] + } + ], + "source": [ + "# Поместим объекты в кортеж и вызовем методы каждого класса\n", + "\n", + "for animal in (cat, dog):\n", + " animal.info() # type: ignore[union-attr]\n", + " animal.sound() # type: ignore[union-attr]\n", + " print()" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "20342e95", + "metadata": {}, + "outputs": [], + "source": [ + "# Решим задачу о среднем росте с помощью класса\n", + "\n", + "patients: list[dict[str, float | str]] = [\n", + " {\"name\": \"Николай\", \"height\": 178},\n", + " {\"name\": \"Иван\", \"height\": 182},\n", + " {\"name\": \"Алексей\", \"height\": 190},\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "15b7bdc1", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим класс для работы с данными DataClass\n", + "\n", + "\n", + "class DataClass:\n", + " \"\"\"Класс для работы с данными и расчета средних значений.\"\"\"\n", + "\n", + " def __init__(self, data: list[dict[str, float | str]]) -> None:\n", + " \"\"\"Инициализация класса с данными.\n", + "\n", + " Args:\n", + " data: Список словарей с данными.\n", + " \"\"\"\n", + " self.data: list[dict[str, float | str]] = data\n", + " self.metric: str = \"\"\n", + " self.__total: float = 0.0\n", + " self.__count: int = 0\n", + "\n", + " def count_average(self, metric: str) -> float:\n", + " \"\"\"Расчет среднего значения по указанной метрике.\n", + "\n", + " Args:\n", + " metric: Название метрики для расчета среднего.\n", + "\n", + " Returns:\n", + " Среднее значение по указанной метрике.\n", + " \"\"\"\n", + " # параметр metric определит, по какому столбцу считать среднее\n", + " self.metric = metric\n", + "\n", + " # объявим два частных атрибута\n", + " self.__total = 0.0\n", + " self.__count = 0\n", + "\n", + " # в цикле for пройдемся по списку словарей\n", + " for item in self.data:\n", + "\n", + " # рассчитем общую сумму по указанному в metric значению\n", + " self.__total += float(item[self.metric])\n", + "\n", + " # и количество таких записей\n", + " self.__count += 1\n", + "\n", + " # разделим общую сумму показателя на количество записей\n", + " return self.__total / self.__count" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "7286e0f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "183.33333333333334" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# создадим объект класса DataClass и передадим ему данные\n", + "data_object: DataClass = DataClass(patients)\n", + "\n", + "# вызовем метод .count_average() с метрикой 'height'\n", + "data_object.count_average(\"height\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72307e13", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/makarov/chapter_9_oop.py b/python/makarov/chapter_9_oop.py new file mode 100644 index 00000000..e7c7c3dc --- /dev/null +++ b/python/makarov/chapter_9_oop.py @@ -0,0 +1,531 @@ +"""ООП. + +Классы и объекты. +""" + +# # Классы и объекты в Питоне + +# + +# создадим класс CatClass +import numpy as np + + +class CatClass: + """Простой класс для демонстрации основ ООП.""" + + def __init__(self) -> None: + """Инициализация объекта класса CatClass.""" + + +# + +# Создание объекта + +# создадим объект Matroskin класса CatClass +Matroskin: CatClass = CatClass() +# - + +# проверим тип данных созданной переменной +type(Matroskin) + +# + +# Атрибуты класса +# Давайте дополним класс CatClass атрибутом типа (type_) +# и атрибутом цвета шерсти (color). + + +# вновь создадим класс CatClass +class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined + """Класс для представления кошки с атрибутами.""" + + def __init__(self, color: str) -> None: + """Инициализация объекта CatClass с цветом. + + Args: + color: Цвет шерсти кошки. + """ + # этот параметр будет записан в переменную атрибута + self.color: str = color + + # значение атрибута type_ задается внутри класса + self.type_: str = "cat" + + +# + +# повторно создадим объект класса CatClass +# передав ему параметр цвета шерсти +Matroskin = CatClass("gray") # type: ignore[call-arg] + +# и выведем атрибуты класса +Matroskin.color, Matroskin.type_ # type: ignore[attr-defined] +# - + +# # Методы класса + +# + +# Дополним класс возможностью выполнять определенные действия + + +# перепишем класс CatClass +class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined + """Класс кошки с методами.""" + + def __init__(self, color: str) -> None: + """Инициализация кошки. + + Args: + color: Цвет шерсти кошки. + """ + self.color: str = color + self.type_: str = "cat" + + def meow(self) -> None: + """Кошка мяукает три раза.""" + for _ in range(3): + print("Мяу") + + def info(self) -> None: + """Выводит информацию об объекте.""" + print(self.color, self.type_) + + +# - + +# создадим объект +Matroskin = CatClass("gray") # type: ignore[call-arg] + +# применим метод .meow() +Matroskin.meow() # type: ignore[attr-defined] + +# и метод .info() +Matroskin.info() # type: ignore[attr-defined] + +# # Принципы объектно-ориентированного программирования + +# + +# Инкапсуляция + +# Инкапсуляция (encapsulation) — это способность класса +# хранить данные и методы внутри себя. + +# + +# Публичные и частные атрибуты класса + +# Публичные атрибуты — это те атрибуты, к которым можно получить +# доступ за пределами «капсулы» класса. + +# + +# Причем не просто получить доступ, но и изменить их. + +# изменим атрибут type_ объекта Matroskin на dog +Matroskin.type_ = "dog" # type: ignore[attr-defined] + +# выведем этот атрибут +Matroskin.type_ # type: ignore[attr-defined] + +# + +# Способ 1. Один символ подчеркивания указывает на приватный атрибут + + +class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined + """Класс кошки с защищенным атрибутом.""" + + def __init__(self, color: str) -> None: + """Инициализация кошки. + + Args: + color: Цвет шерсти кошки. + """ + self.color: str = color + # символ подчеркивания указывает на защищенный атрибут + self._type_: str = "cat" + + +# + +# Способ 2. Двойное подчеркивание перед названием атрибута + + +class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined + """Класс кошки с приватным атрибутом.""" + + def __init__(self, color: str) -> None: + """Инициализация кошки. + + Args: + color: Цвет шерсти кошки. + """ + self.color: str = color + # символ двойного подчеркивания предотвратит доступ извне + self.__type_: str = "cat" # pylint: disable=unused-private-member + + +# + +Matroskin = CatClass("gray") # type: ignore[call-arg] + +# теперь при вызове этого атрибута Питон выдаст ошибку +# Matroskin.__type_ + +# + +# Наследование +# Наследование — это способность класса наследовать атрибуты +# и методы другого класса. + +# + +# создадим класс Animal + + +class Animal: + """Базовый класс для животных.""" + + def __init__(self, weight: float, length: float) -> None: + """Инициализация животного. + + Args: + weight: Вес животного в килограммах. + length: Длина животного в сантиметрах. + """ + self.weight: float = weight + self.length: float = length + + def eat(self) -> None: + """Животное ест.""" + print("Eating") + + def sleep(self) -> None: + """Животное спит.""" + print("Sleeping") + + +# + +# Создадим класс-потомок Bird (птица) + + +# создадим класс Bird, наследующий от Animal +class Bird(Animal): + """Класс для представления птиц.""" + + def move(self) -> None: + """Птица летает.""" + print("Flying") + + +# - + +# создадим объект pigeon и передадим ему значения веса и длины +pigeon: Bird = Bird(0.3, 30) + +# посмотрим на унаследованные у класса Animal атрибуты +pigeon.weight, pigeon.length + +# и методы +pigeon.eat() + +# + +# Кроме того, мы можем вызвать метод, свойственный только классу Bird + +pigeon.move() + +# + +# Функция super() +# Предположим, что в наш класс Bird мы хотим добавить +# атрибут flying_speed (скорость полета). + + +# снова создадим класс Bird +# pylint: disable=function-redefined +class Bird(Animal): # type: ignore[no-redef] + """Класс птицы со скоростью полета.""" + + def __init__(self, weight: float, length: float, flying_speed: float) -> None: + """Инициализация птицы. + + Args: + weight: Вес птицы в килограммах. + length: Длина птицы в сантиметрах. + flying_speed: Скорость полета в км/ч. + """ + # вызовем метод родительского класса Animal + super().__init__(weight, length) + self.flying_speed: float = flying_speed + + def move(self) -> None: + """Птица летает.""" + print("Flying") + + +# + +# Без функции super() класс Bird не знал бы откуда брать +# параметры weight и length. + + +# вновь создадим объект pigeon класса Bird с тремя параметрами +pigeon = Bird(0.3, 30, 100) # type: ignore[call-arg] +# - + +# вызовем как унаследованные, так и собственные атрибуты +pigeon.weight, pigeon.length, pigeon.flying_speed # type: ignore[attr-defined] + +# вызовем унаследованный метод .sleep() +pigeon.sleep() + +# и собственный метод .move() +pigeon.move() + +# + +# Переопределение класса +# Переопределение — это способность класса-потомка +# изменять поведение унаследованных методов. + + +# создадим подкласс Flightless для нелетающих птиц +class Flightless(Bird): # pylint: disable=super-init-not-called + """Класс для нелетающих птиц.""" + + def __init__(self, running_speed: float) -> None: + """Инициализация нелетающей птицы. + + Args: + running_speed: Скорость бега в км/ч. + """ + # у нас остается только один атрибут + # pylint: disable=super-init-not-called + self.running_speed: float = running_speed + + def move(self) -> None: + """Нелетающая птица бежит.""" + print("Running") + + +# - + +# Создадим объект ostrich (страус) класса Flightless +ostrich: Flightless = Flightless(60) + +# Посмотрим на значение атрибута скорости +ostrich.running_speed + +# Теперь посмотрим, переопределился ли метод .move() +ostrich.move() + +# + +# Методы всех родительских классов передаются потомкам. + +# применим метод .eat() класса Animal +ostrich.eat() + +# + +# Множественное наследование + +# Питон позволяет классу наследовать методы двух и более классов. + +# Создадим класс SwimmingBird (водоплавающая птица) + +# + +# создадим родительский класс Fish + + +class Fish: + """Класс для представления рыб.""" + + def swim(self) -> None: + """Рыба плывет.""" + print("Swimming") + + +# + +# и еще один родительский класс Bird + + +class Bird: # type: ignore[no-redef] # pylint: disable=function-redefined + """Класс для представления птиц.""" + + def fly(self) -> None: + """Птица летает.""" + print("Flying") + + +# + +# родительские классы перечисляем в скобках через запятую + + +class SwimmingBird(Fish, Bird): + """Класс для водоплавающих птиц.""" + + +# + +# Создадим объект duck (утка) класса SwimmingBird + +duck: SwimmingBird = SwimmingBird() # type: ignore[call-arg] +# - + +# Утка умеет как летать, так и плавать +duck.fly() # type: ignore[attr-defined] + +duck.swim() + +# + +# Полиморфизм +# Полиморфизм — это способность методов с одинаковыми именами +# вести себя по-разному в разных классах. + +# для чисел '+' является оператором сложения +2 + 2 +# - + +# для строк - оператором объединения +"классы" + " и " + "объекты" + +# + +# Полиморфизм функций +# Полиморфизм функций — это способность одной функции +# работать с разными типами данных. +# - + +len("Программирование на Питоне") + +len(["Программирование", "на", "Питоне"]) + +len({0: "Программирование", 1: "на", 2: "Питоне"}) + +len(np.array([1, 2, 3])) + +# + +# Полиморфизм классов +# Полиморфизм классов — это способность методов с одинаковыми +# именами в разных классах вести себя по-разному. + +# + +# создадим класс котов + + +class CatClass: # type: ignore[no-redef] # pylint: disable=function-redefined + """Класс для представления кошек.""" + + def __init__(self, name: str, color: str) -> None: + """Инициализация кошки. + + Args: + name: Имя кошки. + color: Цвет шерсти кошки. + """ + self.name: str = name + self._type_: str = "кот" + self.color: str = color + + def info(self) -> None: + """Выводит информацию о кошке.""" + print( + f"Меня зовут {self.name}, я {self._type_}, " + f"цвет моей шерсти {self.color}" + ) + + def sound(self) -> None: + """Издает звук кошки.""" + print("Я умею мяукать") + + +# + +# создадим класс собак + + +class DogClass: + """Класс для представления собак.""" + + def __init__(self, name: str, color: str) -> None: + """Инициализация собаки. + + Args: + name: Имя собаки. + color: Цвет шерсти собаки. + """ + self.name: str = name + self._type_: str = "пес" + self.color: str = color + + def info(self) -> None: + """Выводит информацию о собаке.""" + print( + f"Меня зовут {self.name}, я {self._type_}, " + f"цвет моей шерсти {self.color}" + ) + + def sound(self) -> None: + """Издает звук собаки.""" + print("Я умею лаять") + + +# - + +# Создадим объекты этих классов +cat: CatClass = CatClass("Бегемот", "черный") # type: ignore[call-arg] +dog: DogClass = DogClass("Барбос", "серый") + +# + +# Поместим объекты в кортеж и вызовем методы каждого класса + +for animal in (cat, dog): + animal.info() # type: ignore[union-attr] + animal.sound() # type: ignore[union-attr] + print() + +# + +# Решим задачу о среднем росте с помощью класса + +patients: list[dict[str, float | str]] = [ + {"name": "Николай", "height": 178}, + {"name": "Иван", "height": 182}, + {"name": "Алексей", "height": 190}, +] + +# + +# создадим класс для работы с данными DataClass + + +class DataClass: + """Класс для работы с данными и расчета средних значений.""" + + def __init__(self, data: list[dict[str, float | str]]) -> None: + """Инициализация класса с данными. + + Args: + data: Список словарей с данными. + """ + self.data: list[dict[str, float | str]] = data + self.metric: str = "" + self.__total: float = 0.0 + self.__count: int = 0 + + def count_average(self, metric: str) -> float: + """Расчет среднего значения по указанной метрике. + + Args: + metric: Название метрики для расчета среднего. + + Returns: + Среднее значение по указанной метрике. + """ + # параметр metric определит, по какому столбцу считать среднее + self.metric = metric + + # объявим два частных атрибута + self.__total = 0.0 + self.__count = 0 + + # в цикле for пройдемся по списку словарей + for item in self.data: + + # рассчитем общую сумму по указанному в metric значению + self.__total += float(item[self.metric]) + + # и количество таких записей + self.__count += 1 + + # разделим общую сумму показателя на количество записей + return self.__total / self.__count + + +# + +# создадим объект класса DataClass и передадим ему данные +data_object: DataClass = DataClass(patients) + +# вызовем метод .count_average() с метрикой 'height' +data_object.count_average("height") diff --git a/python/oop_molchanov.ipynb b/python/oop_molchanov.ipynb new file mode 100644 index 00000000..de10a5d2 --- /dev/null +++ b/python/oop_molchanov.ipynb @@ -0,0 +1,1431 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "988df063", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"ООП_Молчанов.\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8f701209", + "metadata": {}, + "outputs": [], + "source": [ + "# Объекты и классы\n", + "\n", + "\n", + "class Person:\n", + " \"\"\"Базовый класс Person с атрибутом имени.\"\"\"\n", + "\n", + " name = \"Ivan\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b8b0c27d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Ivan'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Person.name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7689c3c3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Person'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Атрибуты класса\n", + "\n", + "Person.__name__\n", + "\n", + "# название класса" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "164af59b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['__class__',\n", + " '__delattr__',\n", + " '__dict__',\n", + " '__dir__',\n", + " '__doc__',\n", + " '__eq__',\n", + " '__firstlineno__',\n", + " '__format__',\n", + " '__ge__',\n", + " '__getattribute__',\n", + " '__getstate__',\n", + " '__gt__',\n", + " '__hash__',\n", + " '__init__',\n", + " '__init_subclass__',\n", + " '__le__',\n", + " '__lt__',\n", + " '__module__',\n", + " '__ne__',\n", + " '__new__',\n", + " '__reduce__',\n", + " '__reduce_ex__',\n", + " '__repr__',\n", + " '__setattr__',\n", + " '__sizeof__',\n", + " '__static_attributes__',\n", + " '__str__',\n", + " '__subclasshook__',\n", + " '__weakref__',\n", + " 'name']" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Методы класса\n", + "\n", + "dir(Person)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "95e4fb3c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "type" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Тип класса\n", + "\n", + "Person.__class__" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f555c05d", + "metadata": {}, + "outputs": [], + "source": [ + "# Вызов класса возвращает его объект\n", + "\n", + "person = Person()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7c0f60ea", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.Person" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "person.__class__" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "ba7b8763", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Person'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "person.__class__.__name__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fea95d5f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.Person" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# класс как объект\n", + "\n", + "type(person)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "cbe4e00e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.Person at 0x163aaead810>" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "new_person = type(person)()\n", + "\n", + "new_person\n", + "\n", + "# Новый экземпляр класса Person" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "032c82db", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1527580751056, 1527580907536)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# сравним id person и new_person\n", + "person_ids = (id(person), id(new_person))\n", + "person_ids\n", + "\n", + "# Класс - описание объекта" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91b69d11", + "metadata": {}, + "outputs": [], + "source": [ + "# Свойства и функции классов\n", + "# Класс создает свой пространство имен\n", + "# Имена переменных - функции и значения, которые принадлежат классу\n", + "\n", + "\n", + "class PersonV2:\n", + " \"\"\"Класс Person с атрибутом имени.\"\"\"\n", + "\n", + " name = \"Ivan\"\n", + "\n", + "\n", + "# Переменные должны быть объявлены внутри класса" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "5293928d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "mappingproxy({'__module__': '__main__',\n", + " '__firstlineno__': 5,\n", + " 'name': 'Ivan',\n", + " '__static_attributes__': (),\n", + " '__dict__': ,\n", + " '__weakref__': ,\n", + " '__doc__': None})" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Пространство имен класса\n", + "PersonV2.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f6d06d7", + "metadata": {}, + "outputs": [], + "source": [ + "# добавим новое свойство классу\n", + "\n", + "PersonV2.age = 30 # type: ignore[attr-defined]" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "b536e81d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "mappingproxy({'__module__': '__main__',\n", + " '__firstlineno__': 5,\n", + " 'name': 'Ivan',\n", + " '__static_attributes__': (),\n", + " '__dict__': ,\n", + " '__weakref__': ,\n", + " '__doc__': None,\n", + " 'age': 30})" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "PersonV2.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "c3725576", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Ivan'" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Свойства классов можно назначать через специальные функции\n", + "\n", + "getattr(PersonV2, \"name\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "0465e10c", + "metadata": {}, + "outputs": [], + "source": [ + "setattr(PersonV2, \"dob\", 1990)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "458247dc", + "metadata": {}, + "outputs": [], + "source": [ + "delattr(PersonV2, \"dob\")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "3061e59f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "mappingproxy({'__module__': '__main__',\n", + " '__firstlineno__': 5,\n", + " 'name': 'Ivan',\n", + " '__static_attributes__': (),\n", + " '__dict__': ,\n", + " '__weakref__': ,\n", + " '__doc__': None,\n", + " 'age': 30})" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "PersonV2.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "d345981d", + "metadata": {}, + "outputs": [], + "source": [ + "# Объявление функции класса\n", + "\n", + "\n", + "class PersonV3:\n", + " \"\"\"Класс Person с методом приветствия.\"\"\"\n", + "\n", + " name = \"Ivan\"\n", + "\n", + " def greet(self) -> str:\n", + " \"\"\"Greet по имени класса.\"\"\"\n", + " return f\"Hello, {self.__class__.name}!\"" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "ebc0f4c4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Hello, Ivan!'" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "person_v3 = PersonV3()\n", + "person_v3.greet()" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "5ef6d4a0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'__module__': '__main__', '__firstlineno__': 3, 'name': 'Ivan',\n", + " '__static_attributes__': (), '__dict__': , '__weakref__': , '__doc__': None}\n" + ] + } + ], + "source": [ + "# Классы вызываемые объекты\n", + "\n", + "\n", + "class PersonV4:\n", + " \"\"\"Базовый класс Person.\"\"\"\n", + "\n", + " name = \"Ivan\"\n", + "\n", + "\n", + "print(PersonV4.__dict__)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "a2245972", + "metadata": {}, + "outputs": [], + "source": [ + "# Создание экземпляра класса\n", + "\n", + "p1 = PersonV4()" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4c781c48", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.PersonV4 at 0x163aae87620>" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "d2efa560", + "metadata": {}, + "outputs": [], + "source": [ + "p2 = PersonV4()" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "c5ec639e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1527580751392, 1527579159888)" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Сравним id p1 и p2\n", + "person_ids_v4 = (id(p1), id(p2))\n", + "person_ids_v4" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "077cae20", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ivan\n", + "Ivan\n" + ] + } + ], + "source": [ + "# Создаем экземпляры класса, чтобы хранить разные\n", + "# значения одних и тех же свойств\n", + "\n", + "print(p1.name)\n", + "print(p2.name)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "4df6a541", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1527581055360\n", + "1527581055360\n", + "1527581055360\n" + ] + } + ], + "source": [ + "# Свойство name ссылается на одно и то же значение в памяти\n", + "print(id(p1.name))\n", + "print(id(p2.name))\n", + "print(id(PersonV4.name))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d06b2599", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{}\n", + "{}\n" + ] + } + ], + "source": [ + "# Обратимся к содержанию словарей у экземпляров класса\n", + "print(p1.__dict__)\n", + "print(p2.__dict__)\n", + "# Они пусты, так как мы не присваивали значения свойствам\n", + "# экземпляров класса. Python ищет свойства сначала в экземпляре\n", + "# класса, если не находит, то идет в класс и ищет там" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab6532ea", + "metadata": {}, + "outputs": [], + "source": [ + "# Свойства класса создают поведение и состояние объектов по\n", + "# Для этого нужно создавать экземпляры класса, чтобы хранить\n", + "# свое уникальное состояние\n", + "\n", + "p1.name = \"Oleg\"\n", + "p2.name = \"Dima\"\n", + "\n", + "# pylint: disable=attribute-defined-outside-init\n", + "p2.age = 25 # type: ignore[attr-defined]" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "0f266d5e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'name': 'Oleg'}\n", + "{'name': 'Dima', 'age': 25}\n" + ] + } + ], + "source": [ + "print(p1.__dict__)\n", + "print(p2.__dict__)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65a047cd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('Oleg', 'Dima', 25)" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "person_names_ages = (p1.name, p2.name, p2.age) # type: ignore[attr-defined]\n", + "person_names_ages" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "1ed36df7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "mappingproxy({'__module__': '__main__',\n", + " '__firstlineno__': 3,\n", + " 'name': 'Ivan',\n", + " '__static_attributes__': (),\n", + " '__dict__': ,\n", + " '__weakref__': ,\n", + " '__doc__': None})" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "PersonV4.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c90525a5", + "metadata": {}, + "outputs": [], + "source": [ + "# p1.age # AttributeError: 'PersonV4' object has no attribute\n", + "# 'age' и в родительском классе не нашел" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "e16e58c5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('Sergey', 'Sergey')" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Добавление или изменение свойств класса влияет на его\n", + "# экземпляры\n", + "person_a = PersonV4()\n", + "person_b = PersonV4()\n", + "PersonV4.name = \"Sergey\"\n", + "names = (person_a.name, person_b.name)\n", + "names\n", + "# Мы переопределили свойство name класса PersonV4,\n", + "# и оба экземпляра класса получили новое значение по умолчанию" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8e857db", + "metadata": {}, + "outputs": [], + "source": [ + "# классы являются вызываемыми объектами, и при вызове\n", + "# классов мы получаем его экземпляр,\n", + "# у каждого экземпляра и класса есть свое пространство имен\n", + "# которые не связанны между собой\n", + "# Эта особенность позволяет создавать объекты с одинаковым\n", + "# поведением, но с разным состоянием" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3ee1f43", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Функции классов и методы экземпляров классов\n", + "class PersonV5:\n", + " \"\"\"Класс Person с методом приветствия.\"\"\"\n", + "\n", + " def hello(self) -> None:\n", + " \"\"\"Выводит приветствие.\"\"\"\n", + " print(\"hello\")\n", + "\n", + "\n", + "PersonV5.hello\n", + "# Объект функции класса PersonV5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a04bad9b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + ">" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Создадим экземпляр класса и посмотрим на объект функции\n", + "# без вызова\n", + "person_v5 = PersonV5()\n", + "person_v5.hello\n", + "# bound method и указание на экземпляр класса person_v5\n", + "# аттрибут hello переменной person_v5 является связанным методом\n", + "# с объектом hello класса PersonV5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a878fd4a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello\n" + ] + } + ], + "source": [ + "PersonV5.hello(person_v5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10a4dafc", + "metadata": {}, + "outputs": [], + "source": [ + "# person_v5.hello() работает благодаря связыванию метода\n", + "# TypeError: PersonV5.hello() takes 1 positional argument\n", + "# but 2 were given" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ab55f46", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(function, method)" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Посмотрим на типы объекта hello у класса и экземпляра\n", + "types_info = (type(PersonV5.hello), type(person_v5.hello))\n", + "types_info\n", + "# Функции и методы разные классы\n", + "# Функции класса при создании экземпляра класса\n", + "# превращаются в методы экземпляра и связываются с этим\n", + "# экземпляром" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a83766e2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1527587907616, 1527580876608)" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# посмотрим на id объектов\n", + "ids_info = (id(PersonV5.hello), id(person_v5.hello))\n", + "ids_info" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8480ee30", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(['__annotate__',\n", + " '__annotations__',\n", + " '__builtins__',\n", + " '__call__',\n", + " '__class__',\n", + " '__closure__',\n", + " '__code__',\n", + " '__defaults__',\n", + " '__delattr__',\n", + " '__dict__',\n", + " '__dir__',\n", + " '__doc__',\n", + " '__eq__',\n", + " '__format__',\n", + " '__ge__',\n", + " '__getattribute__',\n", + " '__getstate__',\n", + " '__globals__',\n", + " '__gt__',\n", + " '__hash__',\n", + " '__init__',\n", + " '__init_subclass__',\n", + " '__kwdefaults__',\n", + " '__le__',\n", + " '__lt__',\n", + " '__module__',\n", + " '__name__',\n", + " '__ne__',\n", + " '__new__',\n", + " '__qualname__',\n", + " '__reduce__',\n", + " '__reduce_ex__',\n", + " '__repr__',\n", + " '__setattr__',\n", + " '__sizeof__',\n", + " '__str__',\n", + " '__subclasshook__',\n", + " '__type_params__'],\n", + " ['__call__',\n", + " '__class__',\n", + " '__delattr__',\n", + " '__dir__',\n", + " '__doc__',\n", + " '__eq__',\n", + " '__format__',\n", + " '__func__',\n", + " '__ge__',\n", + " '__get__',\n", + " '__getattribute__',\n", + " '__getstate__',\n", + " '__gt__',\n", + " '__hash__',\n", + " '__init__',\n", + " '__init_subclass__',\n", + " '__le__',\n", + " '__lt__',\n", + " '__ne__',\n", + " '__new__',\n", + " '__reduce__',\n", + " '__reduce_ex__',\n", + " '__repr__',\n", + " '__self__',\n", + " '__setattr__',\n", + " '__sizeof__',\n", + " '__str__',\n", + " '__subclasshook__'])" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# у них разные атрибуты\n", + "dir_info = (dir(PersonV5.hello), dir(person_v5.hello))\n", + "dir_info" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "844c55af", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# сперва питон ищет определение имени в локальном пространстве\n", + "# имен экземпляра класса\n", + "person_v5.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89fe2573", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello\n" + ] + } + ], + "source": [ + "PersonV5.hello(person_v5)\n", + "# Вызов функции класса через экземпляр класса" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "659140c4", + "metadata": {}, + "outputs": [], + "source": [ + "# методы или функции нужны для обработки значений,\n", + "# которые были сохранены в объекте - экземпляре класса,\n", + "# а их пространства имен изолированны друг от друга" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5b9cdd0", + "metadata": {}, + "outputs": [], + "source": [ + "# Раз мы вызываем функцию определенную в классе\n", + "# чтобы эта функция работала как нам нужно,\n", + "# мы должны передать ей экземпляр класса\n", + "# в пространстве имен которого сохранено нужное значение\n", + "# Класс может работать со значением экземпляра класса,\n", + "# только получив экземпляр этого класса и обратившись к его\n", + "# свойствам. Для этого python связывает с методом экземпляра\n", + "# сам экземпляр чтобы класс мог обратиться к своему экземпляру\n", + "# и получить доступ к пространству имен (свойствам)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0f0cbe9", + "metadata": {}, + "outputs": [], + "source": [ + "# под капотом python вызывает функцию\n", + "# PersonV5.hello(person_v5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e39da30", + "metadata": {}, + "outputs": [], + "source": [ + "# Методы это классы-обертки, которые объединяют функции\n", + "# класса и конкретные экземпляры класса" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87617a93", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('0x163aad03250', '0x163aad03250')" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hex_ids = (\n", + " hex(id(person_v5)),\n", + " hex(id(person_v5.hello.__self__)), # type: ignore[attr-defined]\n", + ")\n", + "hex_ids\n", + "# это тот же самый объект экземпляра класса person_v5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8289ac31", + "metadata": {}, + "outputs": [], + "source": [ + "# свойстве self была сохранена ссылка на экземпляр класса" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9b4c5d6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# свойстве func сохранены ссылка на функцию hello\n", + "person_v5.hello.__func__ is PersonV5.hello # type: ignore[attr-defined]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc7f09bb", + "metadata": {}, + "outputs": [], + "source": [ + "# при вызове метода из экземпляра класса туда будет\n", + "# передаваться сам экземпляр класса\n", + "\n", + "\n", + "class PersonV6:\n", + " \"\"\"Класс Person для демонстрации параметра self.\"\"\"\n", + "\n", + " def hello(self) -> None:\n", + " \"\"\"Выводит информацию об объекте.\"\"\"\n", + " print(self)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4bf16aac", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<__main__.PersonV6 object at 0x163AB4B5550>\n" + ] + } + ], + "source": [ + "# создаем экземпляр класса\n", + "person_v6 = PersonV6()\n", + "person_v6.hello()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3617af71", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0x163ab4b5550'" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hex(id(person_v6))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a69ade8e", + "metadata": {}, + "outputs": [], + "source": [ + "# первый параметр методов экземпляра класса - self\n", + "\n", + "\n", + "class PersonV7:\n", + " \"\"\"Класс Person с методом приветствия.\"\"\"\n", + "\n", + " def hello(self) -> None:\n", + " \"\"\"Выводит объект.\"\"\"\n", + " print(self)\n", + "\n", + "\n", + "# это только функция которая учитывает, что ее будут вызывать\n", + "# из экземпляра. Методом эта функция станет тогда, когда\n", + "# будет создан экземпляр класса и эта функция будет\n", + "# связанна с этим экземпляром класса, то есть получит\n", + "# ссылку на этот экземпляр" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b8d69a8", + "metadata": {}, + "outputs": [], + "source": [ + "# инициализация экземпляров\n", + "\n", + "\n", + "class PersonV8:\n", + " \"\"\"Класс Person с методами создания и отображения.\"\"\"\n", + "\n", + " def __init__(self) -> None:\n", + " \"\"\"Инициализация.\"\"\"\n", + " self.name: str | None = None # Инициализация\n", + "\n", + " def create(self) -> None:\n", + " \"\"\"Создает атрибут name.\"\"\"\n", + " self.name = \"Ivan\"\n", + "\n", + " def display(self) -> None:\n", + " \"\"\"Выводит имя.\"\"\"\n", + " print(self.name)\n", + "\n", + "\n", + "# self.name = 'Ivan' это тоже самое,\n", + "# что person = PersonV8() person.name = 'Ivan'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd1895a5", + "metadata": {}, + "outputs": [], + "source": [ + "person_v8 = PersonV8()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d906b22c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ivan\n" + ] + } + ], + "source": [ + "# person_v8.display() # AttributeError: 'PersonV8' object\n", + "# has no attribute 'name' потому что локальный словарь dict\n", + "# пустой и имя name в родительском класса не определенно\n", + "# нужно сначала вызвать метод create чтобы создать\n", + "# экземпляру новое свойство\n", + "\n", + "person_v8.create()\n", + "person_v8.display()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b73cbe4", + "metadata": {}, + "outputs": [], + "source": [ + "# для задания свойств экземплярам класса при создании\n", + "# и для присваивания из первоначальных значений\n", + "# то есть для их инициализации есть метод __init__\n", + "# который python вызывает автоматическии при вызове класса,\n", + "# то есть при создании экземпляра\n", + "\n", + "\n", + "class PersonV9:\n", + " \"\"\"Класс Person с инициализацией.\"\"\"\n", + "\n", + " def __init__(self, name: str) -> None:\n", + " \"\"\"Инициализирует имя.\"\"\"\n", + " self.name = name\n", + "\n", + " def display(self) -> None:\n", + " \"\"\"Выводит имя.\"\"\"\n", + " print(self.name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "120a6994", + "metadata": {}, + "outputs": [], + "source": [ + "# создадим экземпляр\n", + "person_v9 = PersonV9(\"ivan\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "488b92cb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'ivan'" + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "person_v9.name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30fd3b1b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'ivan'}" + ] + }, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "person_v9.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d7ae6dd", + "metadata": {}, + "outputs": [], + "source": [ + "# когда создается экземпляр класса\n", + "# питон сначала вызывает метод __new__()\n", + "# который создает сам экземпляр\n", + "# __init__ инициализирует экземпляр свойствами и их значениями\n", + "# делает присвоение на лету, после создания класса" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.14.0)", + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/oop_molchanov.py b/python/oop_molchanov.py new file mode 100644 index 00000000..413b00e3 --- /dev/null +++ b/python/oop_molchanov.py @@ -0,0 +1,411 @@ +"""ООП_Молчанов.""" + +# + +# Объекты и классы + + +class Person: + """Базовый класс Person с атрибутом имени.""" + + name = "Ivan" + + +# - + +Person.name + +# + +# Атрибуты класса + +Person.__name__ + +# название класса + +# + +# Методы класса + +dir(Person) + +# + +# Тип класса + +Person.__class__ + +# + +# Вызов класса возвращает его объект + +person = Person() +# - + +person.__class__ + +person.__class__.__name__ + +# + +# класс как объект + +type(person) + +# + +new_person = type(person)() + +new_person + +# Новый экземпляр класса Person + +# + +# сравним id person и new_person +person_ids = (id(person), id(new_person)) +person_ids + +# Класс - описание объекта + +# + +# Свойства и функции классов +# Класс создает свой пространство имен +# Имена переменных - функции и значения, которые принадлежат классу + + +class PersonV2: + """Класс Person с атрибутом имени.""" + + name = "Ivan" + + +# Переменные должны быть объявлены внутри класса +# - + +# Пространство имен класса +PersonV2.__dict__ + +# + +# добавим новое свойство классу + +PersonV2.age = 30 # type: ignore[attr-defined] +# - + +PersonV2.__dict__ + +# + +# Свойства классов можно назначать через специальные функции + +getattr(PersonV2, "name") +# - + +setattr(PersonV2, "dob", 1990) + +delattr(PersonV2, "dob") + +PersonV2.__dict__ + +# + +# Объявление функции класса + + +class PersonV3: + """Класс Person с методом приветствия.""" + + name = "Ivan" + + def greet(self) -> str: + """Greet по имени класса.""" + return f"Hello, {self.__class__.name}!" + + +# - + +person_v3 = PersonV3() +person_v3.greet() + +# + +# Классы вызываемые объекты + + +class PersonV4: + """Базовый класс Person.""" + + name = "Ivan" + + +print(PersonV4.__dict__) + +# + +# Создание экземпляра класса + +p1 = PersonV4() +# - + +p1 + +p2 = PersonV4() + +# Сравним id p1 и p2 +person_ids_v4 = (id(p1), id(p2)) +person_ids_v4 + +# + +# Создаем экземпляры класса, чтобы хранить разные +# значения одних и тех же свойств + +print(p1.name) +print(p2.name) +# - + +# Свойство name ссылается на одно и то же значение в памяти +print(id(p1.name)) +print(id(p2.name)) +print(id(PersonV4.name)) + +# Обратимся к содержанию словарей у экземпляров класса +print(p1.__dict__) +print(p2.__dict__) +# Они пусты, так как мы не присваивали значения свойствам +# экземпляров класса. Python ищет свойства сначала в экземпляре +# класса, если не находит, то идет в класс и ищет там + +# + +# Свойства класса создают поведение и состояние объектов по +# Для этого нужно создавать экземпляры класса, чтобы хранить +# свое уникальное состояние + +p1.name = "Oleg" +p2.name = "Dima" + +# pylint: disable=attribute-defined-outside-init +p2.age = 25 # type: ignore[attr-defined] +# - + +print(p1.__dict__) +print(p2.__dict__) + +person_names_ages = (p1.name, p2.name, p2.age) # type: ignore[attr-defined] +person_names_ages + +PersonV4.__dict__ + +# + +# p1.age # AttributeError: 'PersonV4' object has no attribute +# 'age' и в родительском классе не нашел +# - + +# Добавление или изменение свойств класса влияет на его +# экземпляры +person_a = PersonV4() +person_b = PersonV4() +PersonV4.name = "Sergey" +names = (person_a.name, person_b.name) +names +# Мы переопределили свойство name класса PersonV4, +# и оба экземпляра класса получили новое значение по умолчанию + +# + +# классы являются вызываемыми объектами, и при вызове +# классов мы получаем его экземпляр, +# у каждого экземпляра и класса есть свое пространство имен +# которые не связанны между собой +# Эта особенность позволяет создавать объекты с одинаковым +# поведением, но с разным состоянием + + +# + +# Функции классов и методы экземпляров классов +class PersonV5: + """Класс Person с методом приветствия.""" + + def hello(self) -> None: + """Выводит приветствие.""" + print("hello") + + +PersonV5.hello +# Объект функции класса PersonV5 +# - + +# Создадим экземпляр класса и посмотрим на объект функции +# без вызова +person_v5 = PersonV5() +person_v5.hello +# bound method и указание на экземпляр класса person_v5 +# аттрибут hello переменной person_v5 является связанным методом +# с объектом hello класса PersonV5 + +PersonV5.hello(person_v5) + +# + +# person_v5.hello() работает благодаря связыванию метода +# TypeError: PersonV5.hello() takes 1 positional argument +# but 2 were given +# - + +# Посмотрим на типы объекта hello у класса и экземпляра +types_info = (type(PersonV5.hello), type(person_v5.hello)) +types_info +# Функции и методы разные классы +# Функции класса при создании экземпляра класса +# превращаются в методы экземпляра и связываются с этим +# экземпляром + +# посмотрим на id объектов +ids_info = (id(PersonV5.hello), id(person_v5.hello)) +ids_info + +# у них разные атрибуты +dir_info = (dir(PersonV5.hello), dir(person_v5.hello)) +dir_info + +# сперва питон ищет определение имени в локальном пространстве +# имен экземпляра класса +person_v5.__dict__ + +PersonV5.hello(person_v5) +# Вызов функции класса через экземпляр класса + +# + +# методы или функции нужны для обработки значений, +# которые были сохранены в объекте - экземпляре класса, +# а их пространства имен изолированны друг от друга + +# + +# Раз мы вызываем функцию определенную в классе +# чтобы эта функция работала как нам нужно, +# мы должны передать ей экземпляр класса +# в пространстве имен которого сохранено нужное значение +# Класс может работать со значением экземпляра класса, +# только получив экземпляр этого класса и обратившись к его +# свойствам. Для этого python связывает с методом экземпляра +# сам экземпляр чтобы класс мог обратиться к своему экземпляру +# и получить доступ к пространству имен (свойствам) + +# + +# под капотом python вызывает функцию +# PersonV5.hello(person_v5) + +# + +# Методы это классы-обертки, которые объединяют функции +# класса и конкретные экземпляры класса +# - + +hex_ids = ( + hex(id(person_v5)), + hex(id(person_v5.hello.__self__)), # type: ignore[attr-defined] +) +hex_ids +# это тот же самый объект экземпляра класса person_v5 + +# + +# свойстве self была сохранена ссылка на экземпляр класса +# - + +# свойстве func сохранены ссылка на функцию hello +person_v5.hello.__func__ is PersonV5.hello # type: ignore[attr-defined] + +# + +# при вызове метода из экземпляра класса туда будет +# передаваться сам экземпляр класса + + +class PersonV6: + """Класс Person для демонстрации параметра self.""" + + def hello(self) -> None: + """Выводит информацию об объекте.""" + print(self) + + +# - + +# создаем экземпляр класса +person_v6 = PersonV6() +person_v6.hello() + +hex(id(person_v6)) + +# + +# первый параметр методов экземпляра класса - self + + +class PersonV7: + """Класс Person с методом приветствия.""" + + def hello(self) -> None: + """Выводит объект.""" + print(self) + + +# это только функция которая учитывает, что ее будут вызывать +# из экземпляра. Методом эта функция станет тогда, когда +# будет создан экземпляр класса и эта функция будет +# связанна с этим экземпляром класса, то есть получит +# ссылку на этот экземпляр + +# + +# инициализация экземпляров + + +class PersonV8: + """Класс Person с методами создания и отображения.""" + + def __init__(self) -> None: + """Инициализация.""" + self.name: str | None = None # Инициализация + + def create(self) -> None: + """Создает атрибут name.""" + self.name = "Ivan" + + def display(self) -> None: + """Выводит имя.""" + print(self.name) + + +# self.name = 'Ivan' это тоже самое, +# что person = PersonV8() person.name = 'Ivan' +# - + +person_v8 = PersonV8() + +# + +# person_v8.display() # AttributeError: 'PersonV8' object +# has no attribute 'name' потому что локальный словарь dict +# пустой и имя name в родительском класса не определенно +# нужно сначала вызвать метод create чтобы создать +# экземпляру новое свойство + +person_v8.create() +person_v8.display() + +# + +# для задания свойств экземплярам класса при создании +# и для присваивания из первоначальных значений +# то есть для их инициализации есть метод __init__ +# который python вызывает автоматическии при вызове класса, +# то есть при создании экземпляра + + +class PersonV9: + """Класс Person с инициализацией.""" + + def __init__(self, name: str) -> None: + """Инициализирует имя.""" + self.name = name + + def display(self) -> None: + """Выводит имя.""" + print(self.name) + + +# - + +# создадим экземпляр +person_v9 = PersonV9("ivan") + +person_v9.name + +person_v9.__dict__ + +# + +# когда создается экземпляр класса +# питон сначала вызывает метод __new__() +# который создает сам экземпляр +# __init__ инициализирует экземпляр свойствами и их значениями +# делает присвоение на лету, после создания класса From 1a9c7f43d4446f0733237ffb4e77612708a87a89 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sun, 21 Dec 2025 19:22:31 +0300 Subject: [PATCH 30/37] [TASK] Python #1 (https://github.com/SENATOROVAI/python/issues/1) Closes https://github.com/SENATOROVAI/python/issues/1 --- python/makarov/chapter_3_if_and_loops.py | 1 + python/makarov/chapter_7_list_tuple_set.py | 1 + python/makarov/chapter_9_oop.ipynb | 42 +++++++++++----------- python/makarov/chapter_9_oop.py | 29 ++++++++------- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/python/makarov/chapter_3_if_and_loops.py b/python/makarov/chapter_3_if_and_loops.py index 82882712..610a4768 100644 --- a/python/makarov/chapter_3_if_and_loops.py +++ b/python/makarov/chapter_3_if_and_loops.py @@ -171,6 +171,7 @@ # + # Функция enumerate() # пусть дан список с днями недели +# pylint: disable=duplicate-code days = [ "Понедельник", "Вторник", diff --git a/python/makarov/chapter_7_list_tuple_set.py b/python/makarov/chapter_7_list_tuple_set.py index 6e99fa21..c3eff499 100644 --- a/python/makarov/chapter_7_list_tuple_set.py +++ b/python/makarov/chapter_7_list_tuple_set.py @@ -119,6 +119,7 @@ # # Распаковка списков # заново создадим список с днями недели +# pylint: disable=duplicate-code week = [ "Понедельник", "Вторник", diff --git a/python/makarov/chapter_9_oop.ipynb b/python/makarov/chapter_9_oop.ipynb index 53637f1f..78ac6026 100644 --- a/python/makarov/chapter_9_oop.ipynb +++ b/python/makarov/chapter_9_oop.ipynb @@ -38,20 +38,20 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "62d87976", "metadata": {}, "outputs": [], "source": [ "# Создание объекта\n", "\n", - "# создадим объект Matroskin класса CatClass\n", - "Matroskin: CatClass = CatClass()" + "# создадим объект matroskin класса CatClass\n", + "matroskin: CatClass = CatClass()" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "81e8ab61", "metadata": {}, "outputs": [ @@ -68,7 +68,7 @@ ], "source": [ "# проверим тип данных созданной переменной\n", - "type(Matroskin)" + "type(matroskin)" ] }, { @@ -102,7 +102,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "e115491e", "metadata": {}, "outputs": [ @@ -120,10 +120,10 @@ "source": [ "# повторно создадим объект класса CatClass\n", "# передав ему параметр цвета шерсти\n", - "Matroskin = CatClass(\"gray\") # type: ignore[call-arg]\n", + "matroskin = CatClass(\"gray\") # type: ignore[call-arg]\n", "\n", "# и выведем атрибуты класса\n", - "Matroskin.color, Matroskin.type_ # type: ignore[attr-defined]" + "matroskin.color, matroskin.type_ # type: ignore[attr-defined]" ] }, { @@ -169,18 +169,18 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "953df6f5", "metadata": {}, "outputs": [], "source": [ "# создадим объект\n", - "Matroskin = CatClass(\"gray\") # type: ignore[call-arg]" + "matroskin = CatClass(\"gray\") # type: ignore[call-arg]" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "87d5d7c8", "metadata": {}, "outputs": [ @@ -196,12 +196,12 @@ ], "source": [ "# применим метод .meow()\n", - "Matroskin.meow() # type: ignore[attr-defined]" + "matroskin.meow() # type: ignore[attr-defined]" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "67c689c0", "metadata": {}, "outputs": [ @@ -215,7 +215,7 @@ ], "source": [ "# и метод .info()\n", - "Matroskin.info() # type: ignore[attr-defined]" + "matroskin.info() # type: ignore[attr-defined]" ] }, { @@ -254,7 +254,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "23a75346", "metadata": {}, "outputs": [ @@ -272,11 +272,11 @@ "source": [ "# Причем не просто получить доступ, но и изменить их.\n", "\n", - "# изменим атрибут type_ объекта Matroskin на dog\n", - "Matroskin.type_ = \"dog\" # type: ignore[attr-defined]\n", + "# изменим атрибут type_ объекта matroskin на dog\n", + "matroskin.type_ = \"dog\" # type: ignore[attr-defined]\n", "\n", "# выведем этот атрибут\n", - "Matroskin.type_ # type: ignore[attr-defined]" + "matroskin.type_ # type: ignore[attr-defined]" ] }, { @@ -329,15 +329,15 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "1a7ff7a0", "metadata": {}, "outputs": [], "source": [ - "Matroskin = CatClass(\"gray\") # type: ignore[call-arg]\n", + "matroskin = CatClass(\"gray\") # type: ignore[call-arg]\n", "\n", "# теперь при вызове этого атрибута Питон выдаст ошибку\n", - "# Matroskin.__type_" + "# matroskin.__type_" ] }, { diff --git a/python/makarov/chapter_9_oop.py b/python/makarov/chapter_9_oop.py index e7c7c3dc..f70b1da2 100644 --- a/python/makarov/chapter_9_oop.py +++ b/python/makarov/chapter_9_oop.py @@ -20,12 +20,12 @@ def __init__(self) -> None: # + # Создание объекта -# создадим объект Matroskin класса CatClass -Matroskin: CatClass = CatClass() +# создадим объект matroskin класса CatClass +matroskin: CatClass = CatClass() # - # проверим тип данных созданной переменной -type(Matroskin) +type(matroskin) # + # Атрибуты класса @@ -53,10 +53,10 @@ def __init__(self, color: str) -> None: # + # повторно создадим объект класса CatClass # передав ему параметр цвета шерсти -Matroskin = CatClass("gray") # type: ignore[call-arg] +matroskin = CatClass("gray") # type: ignore[call-arg] # и выведем атрибуты класса -Matroskin.color, Matroskin.type_ # type: ignore[attr-defined] +matroskin.color, matroskin.type_ # type: ignore[attr-defined] # - # # Методы класса @@ -91,13 +91,13 @@ def info(self) -> None: # - # создадим объект -Matroskin = CatClass("gray") # type: ignore[call-arg] +matroskin = CatClass("gray") # type: ignore[call-arg] # применим метод .meow() -Matroskin.meow() # type: ignore[attr-defined] +matroskin.meow() # type: ignore[attr-defined] # и метод .info() -Matroskin.info() # type: ignore[attr-defined] +matroskin.info() # type: ignore[attr-defined] # # Принципы объектно-ориентированного программирования @@ -116,11 +116,11 @@ def info(self) -> None: # + # Причем не просто получить доступ, но и изменить их. -# изменим атрибут type_ объекта Matroskin на dog -Matroskin.type_ = "dog" # type: ignore[attr-defined] +# изменим атрибут type_ объекта matroskin на dog +matroskin.type_ = "dog" # type: ignore[attr-defined] # выведем этот атрибут -Matroskin.type_ # type: ignore[attr-defined] +matroskin.type_ # type: ignore[attr-defined] # + # Способ 1. Один символ подчеркивания указывает на приватный атрибут @@ -159,10 +159,10 @@ def __init__(self, color: str) -> None: # + -Matroskin = CatClass("gray") # type: ignore[call-arg] +matroskin = CatClass("gray") # type: ignore[call-arg] # теперь при вызове этого атрибута Питон выдаст ошибку -# Matroskin.__type_ +# matroskin.__type_ # + # Наследование @@ -231,8 +231,7 @@ def move(self) -> None: # снова создадим класс Bird -# pylint: disable=function-redefined -class Bird(Animal): # type: ignore[no-redef] +class Bird(Animal): # type: ignore[no-redef] # pylint: disable=function-redefined """Класс птицы со скоростью полета.""" def __init__(self, weight: float, length: float, flying_speed: float) -> None: From fd78a9ff2c089891acb4625dae8b3b3919d507d1 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sun, 21 Dec 2025 19:27:44 +0300 Subject: [PATCH 31/37] [TASK] Python #1 (https://github.com/SENATOROVAI/python/issues/1) Closes https://github.com/SENATOROVAI/python/issues/1 --- python/makarov/chapter_3_if_and_loops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/makarov/chapter_3_if_and_loops.py b/python/makarov/chapter_3_if_and_loops.py index 610a4768..7950bcb7 100644 --- a/python/makarov/chapter_3_if_and_loops.py +++ b/python/makarov/chapter_3_if_and_loops.py @@ -173,7 +173,7 @@ # пусть дан список с днями недели # pylint: disable=duplicate-code days = [ - "Понедельник", + "Monday", "Вторник", "Среда", "Четверг", From 1e31b1754364aee0d3214a34ea767fd2c57bf721 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sun, 21 Dec 2025 19:35:20 +0300 Subject: [PATCH 32/37] [TASK] Python #1 (https://github.com/SENATOROVAI/python/issues/1) Closes https://github.com/SENATOROVAI/python/issues/1 --- python/makarov/chapter_7_list_tuple_set.ipynb | 4 ++-- python/makarov/chapter_7_list_tuple_set.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/python/makarov/chapter_7_list_tuple_set.ipynb b/python/makarov/chapter_7_list_tuple_set.ipynb index bfeabe11..a0e6e924 100644 --- a/python/makarov/chapter_7_list_tuple_set.ipynb +++ b/python/makarov/chapter_7_list_tuple_set.ipynb @@ -496,14 +496,14 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "id": "da5cff90", "metadata": {}, "outputs": [], "source": [ "# заново создадим список с днями недели\n", "week = [\n", - " \"Понедельник\",\n", + " \"Monday\",\n", " \"Вторник\",\n", " \"Среда\",\n", " \"Четверг\",\n", diff --git a/python/makarov/chapter_7_list_tuple_set.py b/python/makarov/chapter_7_list_tuple_set.py index c3eff499..81a6906f 100644 --- a/python/makarov/chapter_7_list_tuple_set.py +++ b/python/makarov/chapter_7_list_tuple_set.py @@ -119,9 +119,8 @@ # # Распаковка списков # заново создадим список с днями недели -# pylint: disable=duplicate-code week = [ - "Понедельник", + "Monday", "Вторник", "Среда", "Четверг", From 1f9e313c3bb4fdd9cc6f46542315e987dc357c84 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sun, 21 Dec 2025 19:43:11 +0300 Subject: [PATCH 33/37] [TASK] Python #1 (https://github.com/SENATOROVAI/python/issues/1) Closes https://github.com/SENATOROVAI/python/issues/1 --- python/makarov/chapter_3_if_and_loops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/makarov/chapter_3_if_and_loops.py b/python/makarov/chapter_3_if_and_loops.py index 7950bcb7..610a4768 100644 --- a/python/makarov/chapter_3_if_and_loops.py +++ b/python/makarov/chapter_3_if_and_loops.py @@ -173,7 +173,7 @@ # пусть дан список с днями недели # pylint: disable=duplicate-code days = [ - "Monday", + "Понедельник", "Вторник", "Среда", "Четверг", From 7afede39b769b7c14f012ea9e497377db49503f8 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sun, 21 Dec 2025 19:48:50 +0300 Subject: [PATCH 34/37] [TASK] Python #1 (https://github.com/SENATOROVAI/python/issues/1) Closes https://github.com/SENATOROVAI/python/issues/1 --- python/makarov/chapter_7_list_tuple_set.ipynb | 12 ++++++------ python/makarov/chapter_7_list_tuple_set.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/python/makarov/chapter_7_list_tuple_set.ipynb b/python/makarov/chapter_7_list_tuple_set.ipynb index a0e6e924..a5774cd0 100644 --- a/python/makarov/chapter_7_list_tuple_set.ipynb +++ b/python/makarov/chapter_7_list_tuple_set.ipynb @@ -504,12 +504,12 @@ "# заново создадим список с днями недели\n", "week = [\n", " \"Monday\",\n", - " \"Вторник\",\n", - " \"Среда\",\n", - " \"Четверг\",\n", - " \"Пятница\",\n", - " \"Суббота\",\n", - " \"Воскресенье\",\n", + " \"Tuesday\",\n", + " \"Wednesday\",\n", + " \"Thursday\",\n", + " \"Friday\",\n", + " \"Saturday\",\n", + " \"Sunday\",\n", "]" ] }, diff --git a/python/makarov/chapter_7_list_tuple_set.py b/python/makarov/chapter_7_list_tuple_set.py index 81a6906f..e3ef7045 100644 --- a/python/makarov/chapter_7_list_tuple_set.py +++ b/python/makarov/chapter_7_list_tuple_set.py @@ -121,12 +121,12 @@ # заново создадим список с днями недели week = [ "Monday", - "Вторник", - "Среда", - "Четверг", - "Пятница", - "Суббота", - "Воскресенье", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", ] # указав индекс элемента, его можно записать в переменную From 31cc18f20fb4d800289d356510ee798cd1899a06 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sun, 21 Dec 2025 19:52:44 +0300 Subject: [PATCH 35/37] Delete github/quiz.ipynb Signed-off-by: Raushan Nigmatullin --- github/quiz.ipynb | 166 ---------------------------------------------- 1 file changed, 166 deletions(-) delete mode 100644 github/quiz.ipynb diff --git a/github/quiz.ipynb b/github/quiz.ipynb deleted file mode 100644 index 39b57c59..00000000 --- a/github/quiz.ipynb +++ /dev/null @@ -1,166 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "44f7f6e5", - "metadata": {}, - "outputs": [], - "source": [ - "\"\"\"[TASK] Контрибьютинг в Open Source #8.\"\"\"" - ] - }, - { - "attachments": { - "image.png": { - "image/png": "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" - } - }, - "cell_type": "markdown", - "id": "ed8d0ce1", - "metadata": {}, - "source": [ - "### GitHub\n", - "1.1. Что такое GitHub?\n", - " - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions\n", - "\n", - "1.2. Как GitHub связан с Git?\n", - " - Git — это распределенная система контроля версий \n", - " - GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями\n", - "\n", - "1.3. Чем отличается fork репозитория от его клонирования (clone)?\n", - " - Fork создает полностью независимую копию репозитория на GitHub-аккаунте\n", - " - Clone создает локальную копию репозитория\n", - "\n", - "1.4. Зачем нужны и как работают pull requests?\n", - " - Pull request (PR) — это механизм слияния своей ветки с основной веткой репозитория для внесения изменений\n", - " - Pull request нужен для ​code review и обсуждения изменений перед слиянием\n", - "\n", - " - Как работает pull request:​\n", - " - Разработчик создает ветку с изменениями\n", - " - Отправляет ветку в репозиторий\n", - " - Открывает pull request через GitHub\n", - " - Команда обсуждает и проверяет код\n", - " - Затем ветки сливают\n", - "\n", - "1.5. GitHub использует ваш почтовый адрес для привязки ваших Git коммитов к вашей учётной записи?\n", - " - Да\n", - "\n", - "1.6 Какая команда генерирует SSH ключ для Доступа по SSH к репозиторию (Рисунок 83)\n", - " - ssh-keygen \n", - "\n", - "### Внесение собственного вклада в проекты\n", - "2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117)\n", - " - Нужно сделать Pull request, указав другой Pull request\n", - " - Это нужно для того, чтобы обеспечить правильный порядок слияния или избежать конфликтов и ошибок\n", - "\n", - "### Рабочий процесс с использованием GitHub\n", - "3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект\n", - " - Форкнуть проект\n", - " - Клонировать форк локально\n", - " - Создать отдельную ветку для изменений\n", - " - Внести изменения\n", - " - Закоммитить изменения\n", - " - Отправить изменения в свой форк\n", - " - Создать Pull Request\n", - " - Подождать проверки\n", - "\n", - "3.1 Какие практики принято соблюдать при создании Pull Request чтобы закрыть автоматический issues?\n", - " - В описании pull request использовать ключевые слова и номер issue:\n", - " - Fixes #номер, Closes #номер, Resolves #номер\n", - "\n", - " - Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues?\n", - " - Использовать те же ключевые слова и номер issue в сообщении коммита\n", - "\n", - "3.2 Как отклонить/закрыть пул реквест? (предоставьте скриншот где это в гитхабе)\n", - " ![image.png](attachment:image.png)\n", - "\n", - "3.3 Перед отправкой пул реквеста нужно ли создавать ишьюс?\n", - " - Нет, это не обязательно, но зависит от практик конкретного проекта\n", - "\n", - "3.4 В какой вкладке можно посмотреть список изменений который был в пул реквесте? (Рисунок 92)\n", - " - Files changed\n", - "\n", - "3.5 В какой вкладке находится страница обсуждений пул реквеста? (Рисунок 94)\n", - " - Conversation\n", - "\n", - "### Создание запроса на слияние\n", - "4 Можно ли открыть пул реквест, если вы ничего не вносили в FORK?\n", - " - Нет\n", - "\n", - "4.1 Что нужно сделать чтобы открыть пул реквест? (Рисунок 90)\n", - " - Сделать форк репозитория и склонировать его\n", - " - Создать новую ветку в репозитории для изменений\n", - " - Внести изменения и закоммитить изменения в этой ветке\n", - " - Отправить (push) ветку с изменениями в форк на GitHub\n", - " - На GitHub после пуша появится нажать \"Compare & pull request\"\n", - " - Создать пул реквест \"Create pull request\"\n", - "\n", - "4.2 Что нужно сделать Если ваш Форк устарел?\n", - " - Обновить форк\n", - " - Добавить исходный репозиторий как удалённый с именем «upstream»\n", - " - Получить (fetch) все изменения из оригинального репозитория\n", - " - Переключиться на основную ветку\n", - " - Синхронизировать изменения merge или rebase\n", - " - Отправить (push) изменения в свой форк\n", - "\n", - "4.3 Что нужно сделать если в пул реквесте имеются конфликты слияния (Рисунок 96)\n", - " - Разрешить конфликты и запушить исправленную версию\n", - " \n", - "### Отрывки кода\n", - "5 Что нужно сделать Для добавления отрывка кода в комментарии к ишьюсу? (Рисунок 104)\n", - " - Отметить его обратными кавычками\n", - "\n", - "5.1 На какую клавишу нажать клавишу чтобы выделенный текст был включён как цитата в ваш комментарий?(Рисунок 105)\n", - " - Клавиша \"r\" или символ \">\"\n", - "\n", - "5.2 Как вставить картинку в ишьюс? (Рисунок 108)\n", - " - Перетащить картинку или скопировать изображение\n", - "\n", - "### Поддержание GitHub репозитория в актуальном состоянии\n", - "6 Как понять что ваш форк устарел?\n", - " - Появится сообщение: This branch is N commits behind progit:master\n", - "\n", - "6.1 Как обновить форк?\n", - " - Sync fork - Update branch\n", - "\n", - "### Добавление участников\n", - "7 Как добавить участников в ваш репозиторий, чтобы команда могла работать над одним репозиторием? (Рисунок 112)\n", - " - Settings - Collaborators - Add collaborator\n", - "\n", - "### Упоминания и уведомления\n", - "8 Какой символ нужен для упоминания кого-либо? (Рисунок 118)\n", - " - Символ \"@\"\n", - "\n", - "8.1 Где находится Центр уведомлений, напишите ссылку (Рисунок 121)\n", - " - https://github.com/notifications\n", - "\n", - "### Особенные файлы\n", - "9 Что такое и зачем нужен файл README\n", - " - Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику\n", - "\n", - "9.1 Что такое и зачем нужен файл CONTRIBUTING (Рисунок 122)\n", - " - Файл CONTRIBUTING — это документ, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта\n", - "\n", - "\n", - "### Управление проектом\n", - "10 Как изменить основную ветку (Рисунок 123)\n", - " - Settings - Default branch\n", - "\n", - "10.1 Как передать проект? какая кнопка? (рисунок 124)\n", - " - Settings - Transfer ownership\n", - "\n", - "10.2 Что такое файл .gitignore?\n", - " - Файл .gitignore — это файл в системе контроля версий Git, который содержит список файлов и папок, которые не должны отслеживаться\n", - "\n" - ] - } - ], - "metadata": { - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From c9f64336f5faff88d9fde8f196fb8ceeb7f95116 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sun, 21 Dec 2025 19:53:05 +0300 Subject: [PATCH 36/37] Delete github/quiz.py Signed-off-by: Raushan Nigmatullin --- github/quiz.py | 148 ------------------------------------------------- 1 file changed, 148 deletions(-) delete mode 100644 github/quiz.py diff --git a/github/quiz.py b/github/quiz.py deleted file mode 100644 index 3957c096..00000000 --- a/github/quiz.py +++ /dev/null @@ -1,148 +0,0 @@ -"""[TASK] Контрибьютинг в Open Source #8.""" - -# ### GitHub -# 1.1. Что такое GitHub? -# - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions -# -# 1.2. Как GitHub связан с Git? -# - Git — это распределенная система контроля версий -# - GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями -# -# 1.3. Чем отличается fork репозитория от его клонирования (clone)? -# - Fork создает полностью независимую копию репозитория на GitHub-аккаунте -# - Clone создает локальную копию репозитория -# -# 1.4. Зачем нужны и как работают pull requests? -# - Pull request (PR) — это механизм слияния своей ветки с основной веткой репозитория для внесения изменений -# - Pull request нужен для ​code review и обсуждения изменений перед слиянием -# -# - Как работает pull request:​ -# - Разработчик создает ветку с изменениями -# - Отправляет ветку в репозиторий -# - Открывает pull request через GitHub -# - Команда обсуждает и проверяет код -# - Затем ветки сливают -# -# 1.5. GitHub использует ваш почтовый адрес для привязки ваших Git коммитов к вашей учётной записи? -# - Да -# -# 1.6 Какая команда генерирует SSH ключ для Доступа по SSH к репозиторию (Рисунок 83) -# - ssh-keygen -# -# ### Внесение собственного вклада в проекты -# 2.1 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV -# -# 2.2 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/tree/dev -# -# 2.4 480 -# -# 2.6 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/pull/1 -# -# 2.7 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/pulls?q=is%3Apr+is%3Aclosed -# -# 2.8 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/tree/af076f522bbf6f2fa41b67df99302a55a779bea6 -# -# 2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117) -# - Нужно сделать Pull request, указав другой Pull request -# - Это нужно для того, чтобы обеспечить правильный порядок слияния или избежать конфликтов и ошибок -# -# ### Рабочий процесс с использованием GitHub -# 3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект -# - Форкнуть проект -# - Клонировать форк локально -# - Создать отдельную ветку для изменений -# - Внести изменения -# - Закоммитить изменения -# - Отправить изменения в свой форк -# - Создать Pull Request -# - Подождать проверки -# -# 3.1 Какие практики принято соблюдать при создании Pull Request чтобы закрыть автоматический issues? -# - В описании pull request использовать ключевые слова и номер issue: -# - Fixes #номер, Closes #номер, Resolves #номер -# -# - Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues? -# - Использовать те же ключевые слова и номер issue в сообщении коммита -# -# 3.2 Как отклонить/закрыть пул реквест? (предоставьте скриншот где это в гитхабе) -# ![image.png](attachment:image.png) -# -# 3.3 Перед отправкой пул реквеста нужно ли создавать ишьюс? -# - Нет, это не обязательно, но зависит от практик конкретного проекта -# -# 3.4 В какой вкладке можно посмотреть список изменений который был в пул реквесте? (Рисунок 92) -# - Files changed -# -# 3.5 В какой вкладке находится страница обсуждений пул реквеста? (Рисунок 94) -# - Conversation -# -# ### Создание запроса на слияние -# 4 Можно ли открыть пул реквест, если вы ничего не вносили в FORK? -# - Нет -# -# 4.1 Что нужно сделать чтобы открыть пул реквест? (Рисунок 90) -# - Сделать форк репозитория и склонировать его -# - Создать новую ветку в репозитории для изменений -# - Внести изменения и закоммитить изменения в этой ветке -# - Отправить (push) ветку с изменениями в форк на GitHub -# - На GitHub после пуша появится нажать "Compare & pull request" -# - Создать пул реквест "Create pull request" -# -# 4.2 Что нужно сделать Если ваш Форк устарел? -# - Обновить форк -# - Добавить исходный репозиторий как удалённый с именем «upstream» -# - Получить (fetch) все изменения из оригинального репозитория -# - Переключиться на основную ветку -# - Синхронизировать изменения merge или rebase -# - Отправить (push) изменения в свой форк -# -# 4.3 Что нужно сделать если в пул реквесте имеются конфликты слияния (Рисунок 96) -# - Разрешить конфликты и запушить исправленную версию -# -# ### Отрывки кода -# 5 Что нужно сделать Для добавления отрывка кода в комментарии к ишьюсу? (Рисунок 104) -# - Отметить его обратными кавычками -# -# 5.1 На какую клавишу нажать клавишу чтобы выделенный текст был включён как цитата в ваш комментарий?(Рисунок 105) -# - Клавиша "r" или символ ">" -# -# 5.2 Как вставить картинку в ишьюс? (Рисунок 108) -# - Перетащить картинку или скопировать изображение -# -# ### Поддержание GitHub репозитория в актуальном состоянии -# 6 Как понять что ваш форк устарел? -# - Появится сообщение: This branch is N commits behind progit:master -# -# 6.1 Как обновить форк? -# - Sync fork - Update branch -# -# ### Добавление участников -# 7 Как добавить участников в ваш репозиторий, чтобы команда могла работать над одним репозиторием? (Рисунок 112) -# - Settings - Collaborators - Add collaborator -# -# ### Упоминания и уведомления -# 8 Какой символ нужен для упоминания кого-либо? (Рисунок 118) -# - Символ "@" -# -# 8.1 Где находится Центр уведомлений, напишите ссылку (Рисунок 121) -# - https://github.com/notifications -# -# ### Особенные файлы -# 9 Что такое и зачем нужен файл README -# - Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику -# -# 9.1 Что такое и зачем нужен файл CONTRIBUTING (Рисунок 122) -# - Файл CONTRIBUTING — это документ, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта -# -# -# ### Управление проектом -# 10 Как изменить основную ветку (Рисунок 123) -# - Settings - Default branch -# -# 10.1 Как передать проект? какая кнопка? (рисунок 124) -# - Settings - Transfer ownership -# -# 10.2 Что такое файл .gitignore? -# - Файл .gitignore — это файл в системе контроля версий Git, который содержит список файлов и папок, которые не должны отслеживаться -# -# From 92c2a06d5d5c4b16a0b2b56887f89b453f23b758 Mon Sep 17 00:00:00 2001 From: Raushan Nigmatullin Date: Sun, 21 Dec 2025 19:56:11 +0300 Subject: [PATCH 37/37] [TASK] Python #1 (https://github.com/SENATOROVAI/python/issues/1) Closes https://github.com/SENATOROVAI/python/issues/1 --- github/quiz.ipynb | 166 ---------------------------------------------- github/quiz.py | 148 ----------------------------------------- 2 files changed, 314 deletions(-) delete mode 100644 github/quiz.ipynb delete mode 100644 github/quiz.py diff --git a/github/quiz.ipynb b/github/quiz.ipynb deleted file mode 100644 index 39b57c59..00000000 --- a/github/quiz.ipynb +++ /dev/null @@ -1,166 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "44f7f6e5", - "metadata": {}, - "outputs": [], - "source": [ - "\"\"\"[TASK] Контрибьютинг в Open Source #8.\"\"\"" - ] - }, - { - "attachments": { - "image.png": { - "image/png": "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" - } - }, - "cell_type": "markdown", - "id": "ed8d0ce1", - "metadata": {}, - "source": [ - "### GitHub\n", - "1.1. Что такое GitHub?\n", - " - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions\n", - "\n", - "1.2. Как GitHub связан с Git?\n", - " - Git — это распределенная система контроля версий \n", - " - GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями\n", - "\n", - "1.3. Чем отличается fork репозитория от его клонирования (clone)?\n", - " - Fork создает полностью независимую копию репозитория на GitHub-аккаунте\n", - " - Clone создает локальную копию репозитория\n", - "\n", - "1.4. Зачем нужны и как работают pull requests?\n", - " - Pull request (PR) — это механизм слияния своей ветки с основной веткой репозитория для внесения изменений\n", - " - Pull request нужен для ​code review и обсуждения изменений перед слиянием\n", - "\n", - " - Как работает pull request:​\n", - " - Разработчик создает ветку с изменениями\n", - " - Отправляет ветку в репозиторий\n", - " - Открывает pull request через GitHub\n", - " - Команда обсуждает и проверяет код\n", - " - Затем ветки сливают\n", - "\n", - "1.5. GitHub использует ваш почтовый адрес для привязки ваших Git коммитов к вашей учётной записи?\n", - " - Да\n", - "\n", - "1.6 Какая команда генерирует SSH ключ для Доступа по SSH к репозиторию (Рисунок 83)\n", - " - ssh-keygen \n", - "\n", - "### Внесение собственного вклада в проекты\n", - "2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117)\n", - " - Нужно сделать Pull request, указав другой Pull request\n", - " - Это нужно для того, чтобы обеспечить правильный порядок слияния или избежать конфликтов и ошибок\n", - "\n", - "### Рабочий процесс с использованием GitHub\n", - "3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект\n", - " - Форкнуть проект\n", - " - Клонировать форк локально\n", - " - Создать отдельную ветку для изменений\n", - " - Внести изменения\n", - " - Закоммитить изменения\n", - " - Отправить изменения в свой форк\n", - " - Создать Pull Request\n", - " - Подождать проверки\n", - "\n", - "3.1 Какие практики принято соблюдать при создании Pull Request чтобы закрыть автоматический issues?\n", - " - В описании pull request использовать ключевые слова и номер issue:\n", - " - Fixes #номер, Closes #номер, Resolves #номер\n", - "\n", - " - Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues?\n", - " - Использовать те же ключевые слова и номер issue в сообщении коммита\n", - "\n", - "3.2 Как отклонить/закрыть пул реквест? (предоставьте скриншот где это в гитхабе)\n", - " ![image.png](attachment:image.png)\n", - "\n", - "3.3 Перед отправкой пул реквеста нужно ли создавать ишьюс?\n", - " - Нет, это не обязательно, но зависит от практик конкретного проекта\n", - "\n", - "3.4 В какой вкладке можно посмотреть список изменений который был в пул реквесте? (Рисунок 92)\n", - " - Files changed\n", - "\n", - "3.5 В какой вкладке находится страница обсуждений пул реквеста? (Рисунок 94)\n", - " - Conversation\n", - "\n", - "### Создание запроса на слияние\n", - "4 Можно ли открыть пул реквест, если вы ничего не вносили в FORK?\n", - " - Нет\n", - "\n", - "4.1 Что нужно сделать чтобы открыть пул реквест? (Рисунок 90)\n", - " - Сделать форк репозитория и склонировать его\n", - " - Создать новую ветку в репозитории для изменений\n", - " - Внести изменения и закоммитить изменения в этой ветке\n", - " - Отправить (push) ветку с изменениями в форк на GitHub\n", - " - На GitHub после пуша появится нажать \"Compare & pull request\"\n", - " - Создать пул реквест \"Create pull request\"\n", - "\n", - "4.2 Что нужно сделать Если ваш Форк устарел?\n", - " - Обновить форк\n", - " - Добавить исходный репозиторий как удалённый с именем «upstream»\n", - " - Получить (fetch) все изменения из оригинального репозитория\n", - " - Переключиться на основную ветку\n", - " - Синхронизировать изменения merge или rebase\n", - " - Отправить (push) изменения в свой форк\n", - "\n", - "4.3 Что нужно сделать если в пул реквесте имеются конфликты слияния (Рисунок 96)\n", - " - Разрешить конфликты и запушить исправленную версию\n", - " \n", - "### Отрывки кода\n", - "5 Что нужно сделать Для добавления отрывка кода в комментарии к ишьюсу? (Рисунок 104)\n", - " - Отметить его обратными кавычками\n", - "\n", - "5.1 На какую клавишу нажать клавишу чтобы выделенный текст был включён как цитата в ваш комментарий?(Рисунок 105)\n", - " - Клавиша \"r\" или символ \">\"\n", - "\n", - "5.2 Как вставить картинку в ишьюс? (Рисунок 108)\n", - " - Перетащить картинку или скопировать изображение\n", - "\n", - "### Поддержание GitHub репозитория в актуальном состоянии\n", - "6 Как понять что ваш форк устарел?\n", - " - Появится сообщение: This branch is N commits behind progit:master\n", - "\n", - "6.1 Как обновить форк?\n", - " - Sync fork - Update branch\n", - "\n", - "### Добавление участников\n", - "7 Как добавить участников в ваш репозиторий, чтобы команда могла работать над одним репозиторием? (Рисунок 112)\n", - " - Settings - Collaborators - Add collaborator\n", - "\n", - "### Упоминания и уведомления\n", - "8 Какой символ нужен для упоминания кого-либо? (Рисунок 118)\n", - " - Символ \"@\"\n", - "\n", - "8.1 Где находится Центр уведомлений, напишите ссылку (Рисунок 121)\n", - " - https://github.com/notifications\n", - "\n", - "### Особенные файлы\n", - "9 Что такое и зачем нужен файл README\n", - " - Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику\n", - "\n", - "9.1 Что такое и зачем нужен файл CONTRIBUTING (Рисунок 122)\n", - " - Файл CONTRIBUTING — это документ, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта\n", - "\n", - "\n", - "### Управление проектом\n", - "10 Как изменить основную ветку (Рисунок 123)\n", - " - Settings - Default branch\n", - "\n", - "10.1 Как передать проект? какая кнопка? (рисунок 124)\n", - " - Settings - Transfer ownership\n", - "\n", - "10.2 Что такое файл .gitignore?\n", - " - Файл .gitignore — это файл в системе контроля версий Git, который содержит список файлов и папок, которые не должны отслеживаться\n", - "\n" - ] - } - ], - "metadata": { - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/github/quiz.py b/github/quiz.py deleted file mode 100644 index 3957c096..00000000 --- a/github/quiz.py +++ /dev/null @@ -1,148 +0,0 @@ -"""[TASK] Контрибьютинг в Open Source #8.""" - -# ### GitHub -# 1.1. Что такое GitHub? -# - GitHub — это веб-платформа для хостинга Git-репозиториев, которая позволяет разработчикам хранить, управлять и совместно работать над кодом. GitHub предоставляет облачное хранилище для репозиториев и включает инструменты для управления проектами, отслеживания проблем (issues), code review и автоматизации через GitHub Actions -# -# 1.2. Как GitHub связан с Git? -# - Git — это распределенная система контроля версий -# - GitHub построен на основе Git и предоставляет веб-интерфейс и дополнительные функции для работы с Git-репозиториями -# -# 1.3. Чем отличается fork репозитория от его клонирования (clone)? -# - Fork создает полностью независимую копию репозитория на GitHub-аккаунте -# - Clone создает локальную копию репозитория -# -# 1.4. Зачем нужны и как работают pull requests? -# - Pull request (PR) — это механизм слияния своей ветки с основной веткой репозитория для внесения изменений -# - Pull request нужен для ​code review и обсуждения изменений перед слиянием -# -# - Как работает pull request:​ -# - Разработчик создает ветку с изменениями -# - Отправляет ветку в репозиторий -# - Открывает pull request через GitHub -# - Команда обсуждает и проверяет код -# - Затем ветки сливают -# -# 1.5. GitHub использует ваш почтовый адрес для привязки ваших Git коммитов к вашей учётной записи? -# - Да -# -# 1.6 Какая команда генерирует SSH ключ для Доступа по SSH к репозиторию (Рисунок 83) -# - ssh-keygen -# -# ### Внесение собственного вклада в проекты -# 2.1 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV -# -# 2.2 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/tree/dev -# -# 2.4 480 -# -# 2.6 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/pull/1 -# -# 2.7 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/pulls?q=is%3Apr+is%3Aclosed -# -# 2.8 https://github.com/nigmatullin244/Data-Science-For-Beginners-from-scratch-SENATOROV/tree/af076f522bbf6f2fa41b67df99302a55a779bea6 -# -# 2.9 Как открыть запрос слияния, указывающий на другой запрос слияния и зачем это нужно? (Рисунок 117) -# - Нужно сделать Pull request, указав другой Pull request -# - Это нужно для того, чтобы обеспечить правильный порядок слияния или избежать конфликтов и ошибок -# -# ### Рабочий процесс с использованием GitHub -# 3 Напишите 8 пунктов, которые нужно сделать, чтобы внести вклад в чужой проект -# - Форкнуть проект -# - Клонировать форк локально -# - Создать отдельную ветку для изменений -# - Внести изменения -# - Закоммитить изменения -# - Отправить изменения в свой форк -# - Создать Pull Request -# - Подождать проверки -# -# 3.1 Какие практики принято соблюдать при создании Pull Request чтобы закрыть автоматический issues? -# - В описании pull request использовать ключевые слова и номер issue: -# - Fixes #номер, Closes #номер, Resolves #номер -# -# - Какие практики принято соблюдать при создании commit чтобы закрыть автоматический issues? -# - Использовать те же ключевые слова и номер issue в сообщении коммита -# -# 3.2 Как отклонить/закрыть пул реквест? (предоставьте скриншот где это в гитхабе) -# ![image.png](attachment:image.png) -# -# 3.3 Перед отправкой пул реквеста нужно ли создавать ишьюс? -# - Нет, это не обязательно, но зависит от практик конкретного проекта -# -# 3.4 В какой вкладке можно посмотреть список изменений который был в пул реквесте? (Рисунок 92) -# - Files changed -# -# 3.5 В какой вкладке находится страница обсуждений пул реквеста? (Рисунок 94) -# - Conversation -# -# ### Создание запроса на слияние -# 4 Можно ли открыть пул реквест, если вы ничего не вносили в FORK? -# - Нет -# -# 4.1 Что нужно сделать чтобы открыть пул реквест? (Рисунок 90) -# - Сделать форк репозитория и склонировать его -# - Создать новую ветку в репозитории для изменений -# - Внести изменения и закоммитить изменения в этой ветке -# - Отправить (push) ветку с изменениями в форк на GitHub -# - На GitHub после пуша появится нажать "Compare & pull request" -# - Создать пул реквест "Create pull request" -# -# 4.2 Что нужно сделать Если ваш Форк устарел? -# - Обновить форк -# - Добавить исходный репозиторий как удалённый с именем «upstream» -# - Получить (fetch) все изменения из оригинального репозитория -# - Переключиться на основную ветку -# - Синхронизировать изменения merge или rebase -# - Отправить (push) изменения в свой форк -# -# 4.3 Что нужно сделать если в пул реквесте имеются конфликты слияния (Рисунок 96) -# - Разрешить конфликты и запушить исправленную версию -# -# ### Отрывки кода -# 5 Что нужно сделать Для добавления отрывка кода в комментарии к ишьюсу? (Рисунок 104) -# - Отметить его обратными кавычками -# -# 5.1 На какую клавишу нажать клавишу чтобы выделенный текст был включён как цитата в ваш комментарий?(Рисунок 105) -# - Клавиша "r" или символ ">" -# -# 5.2 Как вставить картинку в ишьюс? (Рисунок 108) -# - Перетащить картинку или скопировать изображение -# -# ### Поддержание GitHub репозитория в актуальном состоянии -# 6 Как понять что ваш форк устарел? -# - Появится сообщение: This branch is N commits behind progit:master -# -# 6.1 Как обновить форк? -# - Sync fork - Update branch -# -# ### Добавление участников -# 7 Как добавить участников в ваш репозиторий, чтобы команда могла работать над одним репозиторием? (Рисунок 112) -# - Settings - Collaborators - Add collaborator -# -# ### Упоминания и уведомления -# 8 Какой символ нужен для упоминания кого-либо? (Рисунок 118) -# - Символ "@" -# -# 8.1 Где находится Центр уведомлений, напишите ссылку (Рисунок 121) -# - https://github.com/notifications -# -# ### Особенные файлы -# 9 Что такое и зачем нужен файл README -# - Файл README — это текстовый файл, который содержит информацию о проекте, его назначение — предоставлять первичные сведения, которые необходимо прочитать пользователю или разработчику -# -# 9.1 Что такое и зачем нужен файл CONTRIBUTING (Рисунок 122) -# - Файл CONTRIBUTING — это документ, который содержит правила и инструкции для тех, кто хочет внести вклад в разработку проекта -# -# -# ### Управление проектом -# 10 Как изменить основную ветку (Рисунок 123) -# - Settings - Default branch -# -# 10.1 Как передать проект? какая кнопка? (рисунок 124) -# - Settings - Transfer ownership -# -# 10.2 Что такое файл .gitignore? -# - Файл .gitignore — это файл в системе контроля версий Git, который содержит список файлов и папок, которые не должны отслеживаться -# -#