{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python\n", "\n", "## Управление потоком исполнения, функции и модули\n", "\n", "### Раздел 1. Конструкции для управления потоком исполнения\n", "\n", "Мы рассмотрели большую часть конструкций для управления потоком исполнения в лекции 1. Это if-elif-else выражения, циклы while и for ... in. Единственное, что ускользнуло от нашего внимания — условные выражения. Это своего рода ответ на тернарый оператор в языках с C-подобным синтаксисом.\n", "\n", "В общем случае условное выражение выглядит так:\n", "\n", "*выражение* if *условие* else *альтернатива*\n", "\n", "Например:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n" ] } ], "source": [ "for i in range(1, 21):\n", " print(\"Нечетное\" if i & 1 else \"Четное\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Отметим отдельно инструкцию pass, которе позволяет вам использовать ее вместо любого блока кода, где он требуется. Как правило, используется при написании кода, для замены блоков в тех местах, где они еще не написаны, но будут:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "for i in range(1, 10):\n", " pass" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "ename": "SyntaxError", "evalue": "unexpected EOF while parsing (<ipython-input-3-6df1f476ba84>, line 1)", "output_type": "error", "traceback": [ "\u001b[0;36m File \u001b[0;32m\"<ipython-input-3-6df1f476ba84>\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m for i in range(1, 10):\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m unexpected EOF while parsing\n" ] } ], "source": [ "for i in range(1, 10):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для циклов также есть инструкции, нерасмотренные ранее: continue и break. Инструкция continue переходит на следующий этап выполнения цикла, а инструкция break обрывает его выполнение:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Четное\n", "Четное\n", "Четное\n", "Четное\n", "Четное\n", "Четное\n", "Четное\n", "Четное\n", "Четное\n", "Четное\n", "\n", "\n", "\n", "\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n" ] } ], "source": [ "for i in range(1, 21):\n", " if i & 1:\n", " continue\n", " print(\"Нечетное\" if i & 1 else \"Четное\")\n", " \n", "print(\"\\n\\n\\n\")\n", " \n", "for i in range(1, 21):\n", " if i > 5:\n", " break\n", " print(\"Нечетное\" if i & 1 else \"Четное\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "После циклов можно писать блок с else, который будет выполнен в случае нормального завершения цикла:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Цикл завершен нормально\n", "\n", "\n", "\n", "\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n" ] } ], "source": [ "for i in range(1, 21):\n", " print(\"Нечетное\" if i & 1 else \"Четное\")\n", "else:\n", " print(\"Цикл завершен нормально\")\n", " \n", "print(\"\\n\\n\\n\")\n", " \n", "for i in range(1, 21):\n", " if i > 5:\n", " break\n", " print(\"Нечетное\" if i & 1 else \"Четное\")\n", "else:\n", " print(\"Цикл завершен нормально\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Раздел 2. Обработка исключений\n", "\n", "Исключительные ситуации требуют... исключений! Хотя иногда вы можете столнуться с тем, что некоторые вызовы из сторонних библиотек предполагают анализ обработку ошибок в стиле C (возвращение кода ошибки, запись кода ошибки в глобальную переменную и т.д.), но это очень плохой путь. Большой плюс исключений заключается в том, что вы можете проявлять гибкость при их обработке: разным образом обрабатывать исключения разных групп, вешать один обработчик исключений на очень большие группы исключений, обрабатывать их на любом уровне исполнения и т.д.\n", "\n", "Общий синтаксис для ловли и обработки исключений:\n", "\n", "```Python\n", "try:\n", " блок_в_котором_может_возникнуть исключение\n", "except группа_исключений_1 as переменная_1:\n", " обработка_исключений_1\n", "...\n", "except группа_исключений_N as переменная_N:\n", " обработка_исключений_N\n", "else:\n", " блок_который_выполняется_если_исключений_не_было\n", "finally:\n", " блок_который_выполняется_в_любом_случае\n", "```\n", "\n", "Блоки else и finally являются необязательными, но хотя бы один except должен присутствовать. Переменные типа `переменная_N` позволяют доступиться к объекту исключения внутри обработчика.\n", "\n", "Блок finally **всегда** выполняется в конце. **Всегда** подразумевает, что даже если у вас есть return внутри одного из блоков-обработчиков, finally выполнится. Как правило, там размещается сохранение временных результатов и очистка ресурсов (например, закрытие соединения с БД).\n", "\n", "Все блоки except применяются по очереди. В случае возникновения исключения, сначала первый блок проверяет, подходит ли ему это исключение. \"Подходит\" означает, что в данном выражении except либо указан класс этого исключения, либо класс-предок класса этого исключения. Если исключение не подходит, то идет проверка по следующему блоку. Таким образом, следующая конструкция не имеет смысла:\n", "\n", "```Python\n", "try:\n", " какой-то_код\n", "except Класс-предок as e1:\n", " обработка_класса-предка\n", "except Класс-потомок as e2:\n", " обработка_класса-потомка\n", "```\n", "\n", "Всегда будет выполняться обработка класса-предка\n", "\n", "Для возбуждения исключения используется инструкция raise:\n", "\n", "```Python\n", "raise исключение(аргументы)\n", "raise исключение(аргументы) from исходное_исключение\n", "raise\n", "```\n", "\n", "Первая форма возбуждает исключение заданного типа (встроенного или определенного пользователем), который должен быть потомком класса Exception. Вторая и третья форма используются только внутри обработчиков исключений. Вторая форма позволяет возбуждать цепочку исключений, а третья - просто возбуждает то же исключение, которое обрабатывается на данный момент.\n", "\n", "Для определения собственных классов исключений, необходимо просто выбрать класс исключения, от который мы будем наследовать и написать инструкцию:\n", "\n", "```Python\n", "class новое_исключение(исключение_предок): pass\n", "```\n", "\n", "По сути своей это объявление класса (ООП — тема лекции номер 5), у которого нет тела (инструкция pass). После этого объявления вы сможете ловить данные исключения в обработчиках исключений." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Раздел 3. Функции, определяемые пользователем\n", "\n", "В Python есть четыре вида функций: глобальные, локальные, лямбда-функции и методы.\n", "\n", "Все функции, которые вы пишете в своем модуле, определяя их через инструкцию def — глобальные. Локальные функции пишутся таким же образом, но определяются внутри других функций (как правило, это утилитарные функции, которые не имеют смысла за пределами области видимости, в которой они определены). Лямбда-функции (анонимные функции) — функции, которые определяются без привязки к идентификатору. Как привило, лямбда-функции используются в местах, где необходимо передать объект-функцию, но вы не видите необходимости определять отдельную функцию в коде. Методы — функции-члены классов (ООП — тема следующей лекции).\n", "\n", "Мы уже видели стандартное определение функции:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, Bob!\n" ] } ], "source": [ "def greetings(name, greeting):\n", " print(\"{0}, {1}!\".format(greeting, name))\n", " \n", "greetings(\"Bob\", \"Hello\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если мы хотим задать значение по умолчанию какому-либо из аргументов функции, после его имени необходимо поставить = и желаемое значение:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, John!\n" ] } ], "source": [ "def greetings(name, greeting=\"Hello\"):\n", " print(\"{0}, {1}!\".format(greeting, name))\n", " \n", "greetings(\"John\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Нужно учитывать, что параметры со значениями по умолчанию **всегда** должны идти после параметров без значений по умолчанию. При вызове функции, можно явно указать параметры, к которым мы обращаемся, тогда их порядок не важен (только необходимо, чтобы были указаны все параметры без значений по умолчанию):" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Oh hi, Mark!\n" ] } ], "source": [ "greetings(greeting=\"Oh hi\", name=\"Mark\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "При этом данный подход можно комбинировать с классическим: сначала указываем позиционные аргументы, а затем именованные:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "What`s up, Dawg!\n" ] } ], "source": [ "greetings(\"Dawg\", greeting=\"What`s up\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Все параметры, для которых значение по умолчанию не задано, являются обязательными, остальные — необязательными. Необходимо помнить, что если вы создаете новый объект при указании значения по умолчанию, то этот объект будет создан в момент **объявления** функции, а не в момент вызова. Классическая ошибка — задать значение по умолчанию как пустой список. Тогда этот список будет общий на все вызовы функции, т.е. если вы в него что-то запишете в первом вызове, то во втором он уже не будет пустым.\n", "\n", "Имена функций и их параметров согласно PEP 8 рекомендуется называть в camel_case. Следует избегать необщепринятых сокращений в именах функций и параметрах. Имена параметров должны отражать суть того, что в них хранится, а имена функци — суть того, что они делают.\n", "\n", "Можно задокументировать любую функцию при помощи *docstring* — строки, которая идет после строки с инструкцией def, до любой из инструкций внутри функции:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Prints greeting'" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def greetings(name, greeting=\"Hello\"):\n", " \"Prints greeting\"\n", " print(\"{0}, {1}!\".format(greeting, name))\n", " \n", "greetings.__doc__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Естественно, этот строковый литерал может содержать несколько строк" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Prints greeting\n" ] } ], "source": [ "def greetings(name, greeting=\"Hello\"):\n", " \"\"\"Prints \\\n", "greeting\"\"\"\n", " print(\"{0}, {1}!\".format(greeting, name))\n", " \n", "print(greetings.__doc__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "С операторами распаковки мы уже знакомы: \\* позволяет распаковывать коллекции, а \\*\\* — словари, например:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, World!\n", "Hi, Jim!\n" ] } ], "source": [ "lst = [\"World\", \"Hello\"]\n", "dct = {\"greeting\" : \"Hi\", \"name\" : \"Jim\"}\n", "\n", "greetings(*lst)\n", "greetings(**dct)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Анонимные функции\n", "\n", "Синтаксис для описания лямбда функций выглядит следующим образом:\n", "\n", "```Python\n", "lambda аргументы: выражение\n", "```\n", "\n", "Аргументы необязательны, внутри выражения не может содержаться ветвление или циклы, а также инструкции return и yield. При этом можно использовать условные выражения. Результат выражения и является возвращаемым значением лямбда функции. Результатам лямбда-выражения является объект функции:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n", "Нечетное\n", "Четное\n" ] } ], "source": [ "f = lambda x: \"Нечетное\" if x & 1 else \"Четное\"\n", "\n", "for i in range(1, 11):\n", " print(f(i))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Утверждения\n", "\n", "Утверждения (assertions) позволяют проверять, удовлетворяют ли некоторые значения определенным условиям. Синтаксис утверждений следующий:\n", "\n", "```Python\n", "assert выражение, необязательное_выражение\n", "```\n", "\n", "Если выражение имеет булевое значение True, то программа выполняется в обычном режиме. В противном случае возбуждается исключение AssertionError. Если указано необязательное\\_выражение, то оно передается в качестве аргумента AssertionError (полезно для обозначение того, что именно пошло не так). Данные выражения часто используются в тестировании, но, в принципе, можно таким образом проверять корректность, скажем, переданных функции аргументов. Я рекомендуюю в последнем случае все-таки использовать инструкцию throw с более подходящими классами исключений.\n", "\n", "Выражения assert можно игнорировать, передав флаг -0 интерпретатору при запуске программы. Также, можно установить переменную окружения PYTHONOPTIMYZE в 0. При передаче флага -00 интерпретатору будут игнорироваться и инструкции assert, и строки документации (docstrings)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Доступ к переменным в глобальной области видимости\n", "\n", "Иногда не удается избежать наличия глобальных переменных, тогда необходимо явно указать область видимости:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, James!\n" ] } ], "source": [ "greeting = \"Hello\"\n", "\n", "def greetings(name):\n", " global greeting\n", " print(\"{0}, {1}!\".format(greeting, name))\n", " \n", "greetings(\"James\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Также есть возможность указать область видимости nonlocal, тогда будет выбрано значение из ближайшей области видимости, которая находится выше текущей:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Helen\n", "Hello, Natalia\n", "Helen\n", "Hello, Maria\n", "Natalia\n" ] } ], "source": [ "name = \"Helen\"\n", "\n", "def greet1():\n", " name = \"Maria\"\n", " def set_name(new_name):\n", " nonlocal name\n", " name = new_name\n", " set_name(\"Natalia\")\n", " print(\"Hello, \", name)\n", " \n", "def greet2():\n", " name = \"Maria\"\n", " def set_name(new_name):\n", " global name\n", " name = new_name\n", " set_name(\"Natalia\")\n", " print(\"Hello, \", name)\n", " \n", "\n", "print(name)\n", "greet1()\n", "print(name)\n", "greet2()\n", "print(name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видно, в первом случае доступ был к переменной name области видимости функции greet1, а во втором — к переменной name в глобальной области видимости." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Раздел 4. Модули\n", "\n", "Говоря простым языком, модуль в Python — любой .py файл. Пакет модулей — директория, которая содержит пустой файл \\_\\_init\\_\\_.py и некоторое количество .py файлов.\n", "\n", "Импортирование имен из модулей происходит посредством инструкции import. Если после инструкции указать имя модуля, то все имена из этого модуля будут доступны через имя\\_модуля.идентификатор. Модули можно импортировать, присваивая им новый идентификатор в текущей области имен. Также можно импортировать конкретные идентификаторы из модуля. Сущностям из модуля также можно назначать свои идентификаторы. Чтобы импортировать все идентификаторы из модуля в текущее пространство имен, необходимо указать звездочку (астериск, \\*). Вот примеры всех вариантов:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "import os\n", "import math as m\n", "from sys import path\n", "from datetime import date, time\n", "from cmath import sin as s\n", "from requests import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Все модули, которые находятся в пакете являются объектами этого пакета. Т.е. если у вас есть директория Server, внутри которой находится файл \\_\\_init\\_\\_.py и файл, скажем, Router.py, то последний может быть импортирован следующим образом:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import Server.Router as rt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Или:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from Server import Router as rt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Предположим, что вы хотите из модуля Router пакета Server обратиться к модулю Templates того же пакета. Тогда пакет будет доступен по имени . (текущая директория):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "form . import Templates as tmpl" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Раздел 5. Решение домашнего задания\n", "\n", "Рассмотрим решение [задачи](https://lambda-it.ru/post/27).\n", "\n", "Наиболее эффективное и наглядное ее решение — сведение ее к [задаче поиска наибольшей общей подстроки](https://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B0%D1%8F_%D0%BE%D0%B1%D1%89%D0%B0%D1%8F_%D0%BF%D0%BE%D0%B4%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0).\n", "\n", "Для написания нашего решения обратимся к одному из классов разреженных матриц, которые предложены в модуле scipy.sparse. Поскольку наша матрица будет в основном содержать значение 0, то имеет смысл выбрать класс dok_matrix:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "from scipy.sparse import dok_matrix as M" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь напишем нашу основную функцию, которая принимает две последовательности, запоминает, какая из них больше (поскольку в конце мы будем использовать операцию среза, сложность которой возрастает линейно от числа элементов последовательности), создает матрицу вида, требуемого задачей, попутно запоминая местонахождение и значение максимума в ней, а затем возвращает срез от одной из исходных последовательностей:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def longest_common_subsequence(a, b):\n", " n = len(a)\n", " m = len(b)\n", " x = 1\n", " y = 1\n", " maximum = 0\n", " row_based = n >= m\n", " \n", " matrix = M((n + 1, m + 1))\n", " \n", " for i in range(1, n + 1):\n", " for j in range(1, m + 1):\n", " if a[i-1] == b[j-1]:\n", " matrix[i, j] = matrix[i-1, j-1] + 1\n", " tmp = matrix[i, j]\n", " if tmp > maximum:\n", " maximum = tmp\n", " x = i\n", " y = j\n", " maximum = int(maximum)\n", " return a[x - maximum:x] if row_based else b[y - maximum: y]" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[4, 5, 6]\n" ] } ], "source": [ "print(longest_common_subsequence([1, 2, 3, 4, 5, 6, 0], [1, 4, 5, 6, 7, 8]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Раздел 6. Домашнее задание\n", "\n", "Прочитать главы 4 и 5 Саммерфилда, выполнить упражнения после них\n", "\n", "Самостоятельно изучить раздел 5 главы, посвященный стандартной библиотеке Python.\n", "\n", "Переписать игру \"быки и коровы\", разделив ее на 3 модуля: основной, в котором описан интерфейс командной строки и логика игры, модуль, в котором содержатся функции связанные с вариантом, когда компьютер угадывает, и модуль, в котором содержатся функции, связанные с вариантом, когда угадывает игрок.\n", "\n", "Решить задачу, которая будет опубликована в субботу." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.6" } }, "nbformat": 4, "nbformat_minor": 2 }