{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "# Управляющие структуры и функции\n", "\n", " \n", "**С.В. Лемешевский** (email: `sergey.lemeshevsky@gmail.com`), Институт математики НАН Беларуси\n", "\n", "Date: **Mar 18, 2020**\n", "\n", "\n", "\n", "\n", "\n", "\n", "Рассматриваться управляющие структуры языка Python: условные\n", "инструкции и циклы, инструкции обработки исключительных ситуаций.\n", "Кроме того рассматривается создание собственных функций, и здесь будет\n", "подробно рассматриваться чрезвычайно гибкий механизм работы с\n", "аргументами функций." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Управляющие структуры\n", "
\n", "\n", "В языке Python условное ветвление реализуется с помощью инструкции\n", "`if`, а циклическая обработка – с помощью инструкций `while` и `for\n", "... in`. В языке Python имеется также такая конструкция, как условное\n", "выражение – вариант инструкции `if`, аналог трехместного оператора\n", "(`?:`), имеющегося в C-подобных языках." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Условное ветвление\n", "
\n", "\n", "Общий синтаксис инструкции условного ветвления в языке Python имеет\n", "следующий вид:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "if boolean_expression1:\n", " suite1\n", "elif boolean_expression2:\n", " suite2\n", "...\n", "elif boolean_expressionN:\n", " suiteN\n", "else:\n", " else_suite" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Инструкция может содержать ноль или более предложений\n", "`elif`. Заключительное предложение `else` также является\n", "необязательным. Если необходимо предусмотреть ветку для какого-то\n", "особого случая, который не требует никакой обработки, в качестве блока\n", "кода этой ветки можно использовать инструкцию `pass` (она ничего не\n", "делает и просто является инструкцией-заполнителем, используемой там,\n", "где должна находиться хотя бы одна инструкция).\n", "\n", "В некоторых случаях можно сократить инструкцию `if ... else` до\n", "единственного условного выражения. Ниже приводится синтаксис условных\n", "выражений:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "expression1 if boolean_expression else expression2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если логическое выражение `boolean_expression` возвращает значение\n", "`True`, результатом всего условного выражения будет результат\n", "выражения `expression1`, в противном случае – результат выражения\n", "`expression2`.\n", "\n", "\n", "> **Об использовании скобок.**\n", ">\n", "> Предположим, что нам необходимо записать в переменную `width` значение\n", "> 100 и прибавить к нему 10, если переменная margin имеет значение\n", "> `True`. Мы могли бы написать такое выражение:\n", ">" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "width = 100 + 10 if margin else 0\t# ОШИБКА!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> \n", "> Особенно неприятно, что эта строка программного кода работает\n", "> правильно, когда переменная `margin` имеет значение `True`, записывая\n", "> значение 110 в переменную width. Но когда переменная `margin` имеет\n", "> значение `False`, в переменную `width` вместо 100 будет записано\n", "> значение 0. Это происходит потому, что интерпретатор Python\n", "> воспринимает выражение 100 + 10 как часть `expression1` условного\n", "> выражения. Решить эту проблему можно с помощью круглых скобок:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "width = 100 + (10 if margin else 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> \n", "> Кроме того, круглые скобки делают программный код более понятным\n", "> для человека.\n", "\n", "\n", "\n", "\n", "\n", "Условные выражения могут использоваться для видоизменения сообщений,\n", "выводимых для пользователя. Например, при выводе числа обработанных\n", "файлов, вместо того чтобы печатать «0 файл(ов)», «1 файл(ов)» или\n", "что-то подобное, можно было бы использовать пару условных выражений:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "ename": "NameError", "evalue": "name 'count' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"{0} файл{1}\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcount\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m0\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;34m\"нет\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m\"ов\"\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;36m10\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m\"a\"\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;36m10\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;34m\"\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'count' is not defined" ] } ], "source": [ "print(\"{0} файл{1}\".format((count if count != 0 else \"нет\"), (\"ов\" if count % 10 not in [1, 2, 3, 4] else (\"a\" if count % 10 != 1 else \"\"))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Циклы\n", "
\n", "\n", "В языке Python есть две инструкции циклов – `while` и `for ... in`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Циклы `while`\n", "\n", "
\n", "\n", "Ниже приводится полный синтаксис цикла `while`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "while boolean_expression:\n", " while_suite\n", "else:\n", " else_suite" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Предложение `else` является необязательным. До тех пор, пока выражение\n", "`boolean_expression` возвращает значение `True`, в цикле будет\n", "выполняться блок `while_suite`. Если выражение `boolean_expression`\n", "вернет значение `False`, цикл завершится, и при наличии предложения `else`\n", "будет выполнен блок `else_suite`. Если внутри блока `while_suite`\n", "выполняется инструкция `continue`, то управление немедленно передается\n", "в начало цикла и выражение `boolean_expression` вычисляется снова. \n", "Если цикл не завершается нормально, блок предложения `else` не\n", "выполняется.\n", "\n", "Необязательное предложение `else` имеет несколько сбивающее с толку\n", "название, поскольку оно выполняется во всех в случаях, когда цикл\n", "нормально завершается. Если цикл завершается в результате выполнения\n", "инструкции `break` или `return`, когда цикл находится внутри функции\n", "или метода, или в результате исключения, то блок `else_suite` \n", "предложения `else` не выполняется. (При возникновении исключительной\n", "ситуации интерпретатор Python пропускает предложение `else` и пытается\n", "отыскать подходящий обработчик исключения, о чем будет рассказываться\n", "в следующем разделе.) Плюсом такой реализации является одинаковое\n", "поведение предложения `else` в циклах `while`, в циклах `for ... in` и\n", "в блоках `try ... except`. \n", "\n", "Рассмотрим пример, демонстрирующий предложение `else` в действии.\n", "Методы `str.index()` и `list.index()` возвращают индекс заданной\n", "подстроки или элемента или возбуждают исключение `ValueError`, если\n", "подстрока или элемент не найдены. Метод `str.find()` делает то же\n", "самое, но в случае неудачи он не возбуждает исключение, а возвращает\n", "значение `-1`. Для списков не существует эквивалентного метода, но \n", "при желании мы могли бы создать такую функцию, использующую\n", "цикл `while`:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def list_find(lst, target):\n", " index = 0\n", " while index < len(lst):\n", " if lst[index] == target:\n", " break\n", " index += 1\n", " else:\n", " index = -1\n", " return index" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Эта функция просматривает список в поисках заданного элемента\n", "`target`. Если искомый элемент будет найден, инструкция `break`\n", "завершит цикл и вызывающей программе будет возвращен соответствующий\n", "индекс. Если искомый элемент не будет найден, цикл достигнет конца \n", "списка и завершится обычным способом. В случае нормального завер-\n", "шения цикла будет выполнен блок в предложении `else`, индекс получит\n", "значение `-1` и будет возвращен вызывающей программе." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Циклы `for`\n", "
\n", "\n", "Подобно циклу `while`, полный синтаксис цикла `for ... in` также\n", "включает необязательное предложение `else`:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "for expression in iterable:\n", " for_suite\n", "else:\n", " else_suite" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В качестве выражения `expression` обычно используется либо\n", "единственная переменная, либо последовательность переменных, как\n", "правило, в форме кортежа. Если в качестве выражения `expression`\n", "используется кортеж или список, каждый элемент итерируемого объекта\n", "`iterable` распаковывается в элементы `expression`.\n", "\n", "Если внутри блока `for_suite` встретится инструкция `continue`,\n", "управление будет немедленно передано в начало цикла и будет начата\n", "новая итерация. Если цикл завершается по выполнении всех итераций и в\n", "цикле присутствует предложение `else`, выполняется блок\n", "`else_suite`. Если выполнение цикла прерывается принудительно\n", "(инструкцией `break` или `return`), управление немедленно передается\n", "первой инструкции, следующей за циклом, а дополнительное предложение\n", "`else` при этом пропускается. Точно так же, когда возбуждается\n", "исключение, интерпретатор Python пропускает предложение `else` и\n", "пытается отыскать подходящий обработчик исключения (о чем будет\n", "рассказываться в следующем разделе).\n", "\n", "Ниже приводится версия функции `list_find()`, реализованная на базе\n", "цикла `for ... in`, которая так же, как и версия на базе цикла\n", "`while`, демонстрирует предложение `else` в действии:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "ename": "IndentationError", "evalue": "expected an indented block (, line 4)", "output_type": "error", "traceback": [ "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m4\u001b[0m\n\u001b[0;31m break\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mIndentationError\u001b[0m\u001b[0;31m:\u001b[0m expected an indented block\n" ] } ], "source": [ "def list_find(lst, target):\n", " for index, x in enumerate(lst):\n", " if x == target:\n", " break\n", " else:\n", " index = -1\n", " return index" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видно из этого фрагмента, переменные, созданные в выражении\n", "`expression` цикла `for ... in`, продолжают существовать после\n", "завершения цикла. Как и любые локальные переменные, они прекращают\n", "свое существование после выхода из области видимости, включающей их.\n", "\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Обработка исключений\n", "
\n", "\n", "Об ошибках и исключительных ситуациях интерпретатор Python сообщает\n", "посредством возбуждения исключений, хотя в некоторых библиотеках\n", "сторонних разработчиков еще используются устаревшие приемы, такие как\n", "возврат «ошибочного» значения." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Перехват и возбуждение исключений\n", "
\n", "\n", "Перехватывать исключения можно с помощью блоков `try ... except`,\n", "которые имеют следующий синтаксис:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "try:\n", " try_suite\n", "except exception_group1 as variable1:\n", " except_suite1\n", "...\n", "except exception_groupN as variableN:\n", " except_suiteN\n", "else:\n", " else_suite\n", "finally:\n", " finally_suite" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Эта конструкция должна содержать хотя бы один блок except, а блоки\n", "`else` и `finally` являются необязательными. Блок `else_suite`\n", "выполняется, только если блок `try_suite` завершается обычным\n", "способом, и не выполняется в случае возникновения исключения. Если\n", "блок `finally` присутствует, он выполняется всегда и в последнюю\n", "очередь. \n", "\n", "Каждая группа `exception_group` в предложении except может быть\n", "единственным исключением или кортежем исключений в круглых\n", "скобках. Часть `as variable` в каждой группе является\n", "необязательной. В случае ее использования в переменную variable\n", "записывается ссылка на исключение, которое возникло, благодаря этому к\n", "нему можно будет обратиться в блоке `except_suite`.\n", "\n", "Если исключение возникнет во время выполнения блока `try_suite`,\n", "интерпретатор поочередно проверит каждое предложение `except`. Если \n", "будет найдена соответствующая группа `exception_group`, будет выполнен\n", "соответствующий блок `except_suite`. Соответствующей считается группа,\n", "в которой присутствует исключение того же типа, что и возникшее\n", "исключение, или возникшее исключение является подклассом одного из\n", "исключений, перечисленных в группе.\n", "\n", "На рис. [control:except:fig:1](#control:except:fig:1) демонстрируется порядок выполнения\n", "типичной конструкции `try ... except ... finally`. \n", "\n", "\n", "\n", "
\n", "![Порядок выполнения конструкции `try ... except ... finally`](fig-control/tryext_1.png)\n", "\n", "\n", "Ниже приводится окончательная версия функции `list_find()`, на этот\n", "раз она использует механизм обработки исключения:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def list_find(lst, target):\n", " try:\n", " index = lst.index(target)\n", " except ValueError:\n", " index = -1\n", " return index" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Здесь мы использовали конструкцию `try ... except` для преобразования\n", "исключения в возвращаемое значение. Аналогичный подход можно\n", "использовать для перехвата одних исключений и возбуждения других – с\n", "этим приемом мы познакомимся очень скоро. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Возбуждение исключений\n", "
\n", "\n", "Исключения представляют собой удобное средство управления потоком\n", "выполнения. Мы можем воспользоваться этим, используя либо встроенные\n", "исключения, либо создавая свои собственные и возбуждая нужные нам,\n", "когда это необходимо. Возбудить исключение можно одним из двух\n", "способов:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "ename": "NameError", "evalue": "name 'exception' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mexception\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;32mraise\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mNameError\u001b[0m: name 'exception' is not defined" ] } ], "source": [ "raise exception(args)\n", "raise" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В первом случае, то есть когда явно указывается возбуждаемое\n", "исключение, оно должно быть либо встроенным, либо нашим собственным,\n", "наследующим класс `Exception`. Если исключению в виде аргумента\n", "передается некоторый текст, этот текст будет выведен на экран, если\n", "исключение не будет обработано программой. Во втором случае, то есть \n", "когда исключение не указывается, инструкция `raise` повторно возбудит\n", "текущее активное исключение, а в случае отсутствия активного\n", "исключения будет возбуждено исключение `TypeError`. \n", "\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Собственные функции\n", "
\n", "\n", "Функции представляют собой средство, дающее возможность упаковывать и\n", "параметризовать функциональность. В языке Python можно создать четыре\n", "типа функций: глобальные функции, локальные функции, лямбда-функции и\n", "методы. \n", "\n", "Все функции, которые мы создавали до сих пор, являются *глобальными*\n", "функциями. Глобальные объекты (включая функции) доступны из любой\n", "точки программного кода в том же модуле (то есть в том же самом файле\n", "`.py`), которому принадлежит объект. Глобальные объекты доступны\n", "также и из других модулей, как будет показано в следующей главе.\n", "\n", "*Локальные* функции (их еще называют вложенными функциями) –\n", "это функции, которые объявляются внутри других функций. Эти\n", "функции видимы только внутри тех функций, где они были объявлены – они\n", "особенно удобны для создания небольших вспомогательных функций,\n", "которые нигде больше не используются. \n", "\n", "*Лямбда*-функции – это выражения, поэтому они могут создаваться\n", "непосредственно в месте их использования; они имеют множество\n", "ограничений по сравнению с обычными функциями. Методы – это те же\n", "функции, которые ассоциированы с определенным типом данных и могут\n", "использоваться только в связке с этим типом данных.\n", "\n", "Синтаксис создания функции (глобальной или локальной) имеет следующий\n", "вид:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def functionName(parameters):\n", " suite" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Параметры `parameters` являются необязательными и при наличии более\n", "одного параметра записываются как последовательность идентификаторов\n", "через запятую или в виде последовательности пар `identifier=value`, о\n", "чем вскоре будет говориться подробнее. Например, ниже приводится\n", "функция, которая вычисляет площадь треугольника по формуле Герона:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def heron(a, b, c):\n", " s = (a + b + c) / 2\n", " return math.sqrt(s * (s - a) * (s - b) * (s - c))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Внутри функции каждый параметр, `a`, `b` и `c`, инициализируется\n", "соответствующими значениями, переданными в виде аргументов. При вызове\n", "функции мы должны указать все аргументы, например, `heron(3, 4, 5)`.\n", "Если передать слишком мало или слишком много аргументов, будет\n", "возбуждено исключение `TypeError`. Производя такой вызов, мы \n", "говорим, что используем позиционные аргументы, потому что каждый\n", "переданный аргумент становится значением параметра в соответствующей\n", "позиции. То есть в данном случае при вызове функции параметр `a`\n", "получит значение `3`, параметр `b` – значение `4` и параметр `с` –\n", "значение `5`.\n", "\n", "Все функции в языке Python возвращают какое-либо значение, хотя\n", "вполне возможно (и часто так и делается) просто игнорировать это\n", "значение. Возвращаемое значение может быть единственным значением или\n", "кортежем значений, а сами значения могут быть коллекциями, \n", "поэтому практически не существует никаких ограничений на то, что\n", "могут возвращать функции. Мы можем покинуть функцию в любой\n", "момент, используя инструкцию `return`. Если инструкция `return`\n", "используется без аргументов или если мы вообще не используем\n", "инструкцию `return`, функция будет возвращать значение `None`.\n", "\n", "Некоторые функции имеют параметры, для которых может существо-\n", "вать вполне разумное значение по умолчанию. Например, ниже при-\n", "водится функция, которая подсчитывает количество алфавитных сим-\n", "волов в строке; по умолчанию подразумеваются алфавитные символы\n", "из набора ASCII:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "ename": "NameError", "evalue": "name 'string' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mletter_count\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtext\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mletters\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mstring\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mascii_letters\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mletters\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfrozenset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mletters\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mchar\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mtext\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mchar\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mletters\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mNameError\u001b[0m: name 'string' is not defined" ] } ], "source": [ "def letter_count(text, letters=string.ascii_letters):\n", " letters = frozenset(letters)\n", " count = 0\n", " for char in text:\n", " if char in letters:\n", " count += 1\n", " return count" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Здесь при помощи синтаксиса `parameter=default` было определено\n", "значение по умолчанию для параметра `letters`. Это позволяет вызывать \n", "функцию `letter_count()` с единственным аргументом, например,\n", "`letter_count(\"Maggie and Hopey\")`. В этом случае внутри функции\n", "параметр `letter` будет содержать строку, которая была задана как\n", "значение по умолчанию. Но за нами сохраняется возможность изменить\n", "значение по умолчанию, например, указав дополнительный позиционный\n", "аргумент: `letter_count(\"Maggie and Hopey\", \"aeiouAEIOU\")`, или\n", "используя именованный аргумент (об именованных аргументах\n", "рассказывается ниже): `letter_count(\"Maggie and Hopey\", letters=\"aeiouAEIOU\")`. \n", "\n", "Синтаксис параметров не позволяет указывать параметры, не имеющие\n", "значений по умолчанию, после параметров со значениями по умолчанию,\n", "поэтому такое определение: `def bad(a, b=1, c):`, будет вызывать\n", "синтаксическую ошибку. С другой стороны, мы не обязаны передавать\n", "аргументы в том порядке, в каком они указаны в определении функции –\n", "мы можем использовать именованные аргументы и передавать их в виде\n", "`name=value`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Распаковывание аргументов и параметров\n", "
\n", "\n", "В предыдущей главе мы видели, что для передачи позиционных аргументов\n", "можно использовать оператор распаковывания последовательностей\n", "(`*`). Например, если возникает необходимость вычислить площадь\n", "треугольника, а длины всех его сторон хранятся в списке, то мы могли\n", "бы вызвать функцию так: `heron(sides[0], sides[1], sides[2])`, или\n", "просто распаковать список и сделать вызов намного проще:\n", "`heron(*sides)`. Если элементов в списке (или в другой\n", "последовательности) больше, чем параметров в функции, мы можем\n", "воспользоваться операцией извлечения среза, чтобы извлечь нужное число\n", "аргументов.\n", "\n", "Мы можем также использовать оператор распаковывания последовательности\n", "в списке параметров функции. Это удобно, когда необходимо создать\n", "функцию, которая может принимать переменное число позиционных\n", "аргументов. Ниже приводится функция `product()`, которая \n", "вычисляет произведение своих аргументов:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def product(*args):\n", " result = 1\n", " for arg in args:\n", " result *= arg\n", " return result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Эта функция имеет единственный аргумент с именем `args`. Наличие\n", "символа `*` перед ним означает, что внутри функции параметр `args`\n", "обретает форму кортежа, значениями элементов которого будут значения\n", "переданных аргументов. Ниже приводятся несколько примеров вызова\n", "функции:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "product(1, 2, 3, 4) # args == (1, 2, 3, 4); вернет: 24\n", "product(5, 3, 8) # args == (5, 3, 8); вернет: 120\n", "product(11) \t # args == (11,); вернет: 11" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Мы можем использовать именованные аргументы вслед за позиционными, как\n", "в функции, которая приводится ниже, вычисляющей сумму своих\n", "аргументов, каждый из которых возводится в заданную степень:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def sum_of_powers(*args, power=1):\n", " result = 0\n", " for arg in args:\n", " result += arg ** power\n", " return result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Эта функция может вызываться только с позиционными аргументами,\n", "например: `sum_of_powers(1, 3, 5)`, или как с позиционными, так и с\n", "именованным аргументами, например: `sum_of_powers(1, 3, 5, power=2)`.\n", "\n", "Допускается также использовать символ `*` в качестве самостоятельного\n", "«параметра». В данном случае он указывает, что после символа `*` не\n", "может быть других позиционных параметров, однако указание именованных\n", "аргументов допускается. Ниже приводится модифицированная версия\n", "функции `heron()`. На этот раз функция принимает точно три позиционных\n", "аргумента и один необязательный именованный аргумент." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def heron2(a, b, c, *, units=\"meters\"):\n", " s = (a + b + c) / 2\n", " area = math.sqrt(s * (s - a) * (s - b) * (s - c))\n", " return \"{0} {1}\".format(area, units)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ниже приводятся несколько примеров вызовов функции:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "heron2(25, 24, 7)\t\t # вернет: '84.0 meters'\n", "heron2(41, 9, 40, units=\"inches\") # вернет: '180.0 inches'\n", "heron2(25, 24, 7, \"inches\")\t # ОШИБКА! Возбудит исключение TypeError" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В третьем вызове мы попытались передать четыре позиционных аргумента,\n", "но оператор `*` не позволяет этого и вызывает исключение `TypeError`.\n", "\n", "Поместив оператор `*` первым в списке параметров, мы тем самым\n", "полностью запретим использование любых позиционных аргументов и\n", "вынудим тех, кто будет вызывать ее, использовать именованные\n", "аргументы. Ниже приводится пример сигнатуры такой (вымышленной)\n", "функции:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def print_setup(*, paper=\"Letter\", copies=1, color=False):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Мы можем вызывать функцию `print_setup()` без аргументов, допуская\n", "использование значений по умолчанию. Или изменить некоторые или\n", "все значения по умолчанию, например:\n", "`print_setup(paper=\"A4\", color=True)`. Но если мы попытаемся\n", "использовать позиционные аргументы, например: `print_setup(\"A4\")`,\n", "будет возбуждено исключение `TypeError`.\n", "\n", "Так же, как мы распаковываем последовательности для заполнения\n", "позиционных параметров, можно распаковывать и отображения –\n", "с помощью оператора распаковывания отображений (`**`). Мы можем\n", "использовать оператор `**`, чтобы передать содержимое словаря в\n", "функцию `print_setup()`. Например:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "options = dict(paper=\"A4\", color=True)\n", "print_setup(**options)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В данном случае пары «ключ-значение» словаря `options` будут\n", "распакованы, и каждое значение будет ассоциировано с параметром, чье \n", "имя соответствует ключу этого значения. Если в словаре обнаружится\n", "ключ, не совпадающий ни с одним именем параметра, будет возбуждено\n", "исключение `TypeError`. Любые аргументы, для которых в словаре не\n", "найдется соответствующего элемента, получат значение по умолчанию, но\n", "если такие аргументы не имеют значения по умолчанию, будет возбуждено\n", "исключение `TypeError`. \n", "\n", "Кроме того, имеется возможность использовать оператор распаковывания\n", "вместе с параметрами в объявлении функции. Это позволяет создавать\n", "функции, способные принимать любое число именованных аргументов. Ниже\n", "приводится функция `add_person_details()`, которая принимает номер\n", "карточки социального страхования и фамилию в виде позиционных\n", "аргументов, а также произвольное число именованных аргументов:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def add_person_details(ssn, surname, **kwargs):\n", " print(\"SSN =\", ssn)\n", " print(\" surname =\", surname)\n", " for key in sorted(kwargs):\n", " print(\" {0} = {1}\".format(key, kwargs[key]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Эта функция может вызываться как только с двумя позиционными\n", "аргументами, так и с дополнительной информацией, например:\n", "`add_person_details(83272171, \"Luther\", forename=\"Lexis\", age=47)`.\n", "Такая возможность обеспечивает огромную гибкость. Конечно, мы можем\n", "также одновременно принимать переменное число позиционных аргументов и\n", "переменное число именованных аргументов:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "ename": "IndentationError", "evalue": "expected an indented block (, line 3)", "output_type": "error", "traceback": [ "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m3\u001b[0m\n\u001b[0;31m print(\"positional argument {0} = {1}\".format(i, arg))\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mIndentationError\u001b[0m\u001b[0;31m:\u001b[0m expected an indented block\n" ] } ], "source": [ "def print_args(*args, **kwargs):\n", " for i, arg in enumerate(args):\n", " print(\"positional argument {0} = {1}\".format(i, arg))\n", " for key in kwargs:\n", " print(\"keyword argument {0} = {1}\".format(key, kwargs[key]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Эта функция просто выводит полученные аргументы. Она может вызываться\n", "вообще без аргументов или с произвольным числом позиционных и\n", "именованных аргументов." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Доступ к переменным в глобальной области видимости\n", "
\n", "\n", "Иногда бывает удобно иметь несколько глобальных переменных, доступных\n", "из разных функций программы. В этом нет ничего плохого, если речь идет\n", "о «константах», но в случае переменных – это не самый лучший выход,\n", "хотя для коротких одноразовых программ это в некоторых случаях можно\n", "считать допустимым.\n", "\n", "Программа [digit_names.py](src-control/digit_names.py) принимает\n", "необязательный код языка (`en` или `ru`) и число в виде аргументов\n", "командной строки и выводит названия всех цифр заданного числа. То есть\n", "если в командной строке программе было передано число `123`, она\n", "выведет `one two three`. В программе имеется три глобальные\n", "переменные:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "Language = \"en\"\n", "\n", "ENGLISH = {0: \"zero\", 1: \"one\", 2: \"two\", 3: \"three\", 4: \"four\",\n", " 5: \"five\", 6: \"six\", 7: \"seven\", 8: \"eight\", 9: \"nine\"}\n", "RUSSIAN = {0: \"ноль\", 1: \"один\", 2: \"два\", 3: \"три\", 4: \"четыре\",\n", " 5: \"пять\", 6: \"шесть\", 7: \"семь\", 8: \"восемь\", 9: \"девять\"}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Мы следуем соглашению, в соответствии с которым имена переменных,\n", "играющих роль констант, записываются только символами верхнего\n", "регистра, и установили английский язык по умолчанию.\n", "\n", "В некотором другом месте программы выполняется обращение к переменной\n", "`Language`, и ее значение используется при выборе соответствующего\n", "словаря:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def print_digits(digits):\n", " dictionary = ENGLISH if Language == \"en\" else RUSSIAN\n", " for digit in digits:\n", " print(dictionary[int(digit)], end=\" \")\n", " print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Когда интерпретатор Python встречает имя переменной `Language` внутри\n", "функции, он пытается отыскать его в локальной области видимости (в\n", "области видимости функции) и не находит. Поэтому он продолжает поиск в\n", "глобальной области видимости (в области видимости файла `.py`), где и\n", "обнаруживает его.\n", "\n", "Ниже приводится содержимое функции `main()` программы. Она изменяет\n", "значение переменной `Language` в случае необходимости и вызывает \n", "функцию `print_digits()` для вывода результата." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def main():\n", " if len(sys.argv) == 1 or sys.argv[1] in {\"-h\", \"--help\"}:\n", " print(\"usage: {0} [en|ru] number\".format(sys.argv[0]))\n", " sys.exit()\n", "\n", " args = sys.argv[1:]\n", " if args[0] in {\"en\", \"ru\"}:\n", " global Language\n", " Language = args.pop(0)\n", " print_digits(args.pop(0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Обратите внимание на использование инструкции `global` в этой\n", "функции. Эта инструкция используется для того, чтобы сообщить\n", "интерпретатору, что данная переменная существует в глобальной области \n", "видимости (в области видимости файла `.py`) и что операция\n", "присваивания должна применяться к глобальной переменной; без этой\n", "инструкции операция присваивания создаст локальную переменную с тем же\n", "именем.\n", "\n", "> **Замечание.**\n", ">\n", "> Если не использовать инструкцию `global`, программа сохранит\n", "> свою работоспособность, но когда интерпретатор встретит переменную\n", "> `Language` в условной инструкции `if`, он попытается отыскать ее в\n", "> локальной области видимости (в области видимости функции) и, не\n", "> обнаружив ее, создаст новую локальную переменную с именем `Language`,\n", "> оставив глобальную переменную `Language` без изменений. Эта\n", "> малозаметная ошибка будет проявляться только в случае запуска\n", "> программы с аргументом `ru`, потому что в этом случае будет создана\n", "> новая локальная переменная `Language`, в которую будет записано\n", "> значение `ru`, а глобальная переменная `Language`, которая\n", "> используется функцией `print_digits()`, по-прежнему будет иметь\n", "> значение `en`.\n", "\n", "\n", "\n", "\n", "\n", "\n", "В сложных программах лучше вообще не использовать глобальные\n", "переменные, за исключением констант, которые не требуют употребления\n", "инструкции `global`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Лямбда-функции\n", "
\n", "\n", "Лямбда-функции – это функции, для создания которых используется\n", "следующий синтаксис:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "lambda parameters: expression" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Часть `parameters` является необязательной, а если она\n", "присутствует, то обычно представляет собой простой\n", "список имен переменных, разделенных запятыми, то\n", "есть позиционных аргументов, хотя при необходимости\n", "допускается использовать полный синтаксис определе-\n", "ния аргументов, используемый в инструкции `def`. Выражение\n", "`expression` не может содержать условных инструкций или циклов (хотя\n", "условные выражения являются допустимыми), а также не может содержать\n", "инструкцию `return` (или `yield`). Результатом лямбда-выражения\n", "является анонимная функция. Когда вызывается лямбда-функция, она\n", "возвращает результат вычисления выражения `expression`. Если выражение\n", "expression представляет собой кортеж, оно должно быть заключено в\n", "круглые скобки.\n", "\n", "Ниже приводится пример простой лямбда-функции, которая добавляет (или\n", "не добавляет) суффикс `s` в зависимости от того, имеет ли аргумент\n", "значение 1:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "s = lambda x: \"\" if x == 1 else \"s\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Лямбда-выражение возвращает анонимную функцию, которая присваивается\n", "переменной `s`. Любая (вызываемая) переменная может вызываться как\n", "функция при помощи круглых скобок, поэтому после выполнения некоторой\n", "операции можно при помощи функции `s()` вывести сообщение с числом\n", "обработанных файлов, например:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "print(\"{0} file{1} processed\".format(count, s(count)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Лямбда-функции часто используются в виде аргумента `key` встроенной\n", "функции `sorted()` или метода `list.sort()`. Предположим, что имеется\n", "список, элементами которого являются трехэлементные кортежи (номер\n", "группы, порядковый номер, название), и нам необходимо отсортировать\n", "этот список различными способами. Ниже приводится пример такого\n", "списка:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "elements = [(2, 12, \"Mg\"), (1, 11, \"Na\"), (1, 3, \"Li\"), (2, 4, \"Be\")]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Отсортировав список, мы получим следующий результат:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "[(1, 3, 'Li'), (1, 11, 'Na'), (2, 4, 'Be'), (2, 12, 'Mg')]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ранее, когда мы рассматривали функцию `sorted()`, то видели, что\n", "имеется возможность изменить порядок сортировки, если в аргументе `key`\n", "передать требуемую функцию. Например, если необходимо отсортировать\n", "список не по естественному порядку: номер группы, порядковый номер и\n", "название, а по порядковому номеру и названию, то мы могли бы написать\n", "маленькую функцию `def ignore0(e): return e[1], e[2]` и передавать ее\n", "в аргументе `key`. Но создавать в программе массу крошечных функций,\n", "подобных этой, очень неудобно, поэтому часто используется\n", "альтернативный подход, основанный на применении лямбда-функций:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "elements.sort(key=lambda e: (e[1], e[2]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Здесь в качестве значения аргумента key используется выражение\n", "`lambda e: (e[1], e[2])`, которому в виде аргумента e последовательно\n", "передаются все трехэлементные кортежи из списка. Круглые скобки,\n", "окружающие лямбда-выражение, обязательны, когда выражение является\n", "кортежем и лямбда-функция создается как аргумент другой функции. Для\n", "достижения того же эффекта можно было бы использовать операцию\n", "извлечения среза:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "elements.sort(key=lambda e: e[1:3])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Немного более сложная версия обеспечивает возможность сортировки\n", "по названию, без учета регистра символов, и порядковому номеру:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "elements.sort(key=lambda e: (e[2].lower(), e[1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Утверждения\n", "
\n", "\n", "Что произойдет, если функция получит аргументы, имеющие ошибочные\n", "значения? Что случится, если в реализации алгоритма будет допущена\n", "ошибка и вычисления будут выполнены неправильно? Самое неприятное, что\n", "может произойти, – это то, что программа будет выполняться без\n", "каких-либо видимых проблем, но будет давать неверные результаты. Один\n", "из способов избежать таких коварных проблем состоит в том, чтобы\n", "писать тесты. Другой способ состоит в том, чтобы определить\n", "предварительные условия и ожидаемый конечный результат, и сообщать об\n", "ошибке, если они не соответствуют друг другу. В идеале следует\n", "использовать как тестирование, так и метод на основе сравнения\n", "предварительных условий и ожидаемых результатов.\n", "\n", "Предварительные условия и ожидаемый результат можно задать с помощью\n", "инструкции `assert`, которая имеет следующий синтаксис:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "assert boolean_expression, optional_expression" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если выражение `boolean_expression` возвращает значение `False`,\n", "возбуждается исключение `AssertionError`. Если задано необязательное\n", "выражение `optional_expression`, оно будет использовано в качестве\n", "аргумента исключения `AssertionError`, что удобно для передачи сообщений\n", "об ошибках. Однако следует отметить, что утверждения предназначены \n", "для использования разработчиками, а не конечными пользователями.\n", "Проблемы, возникающие в процессе нормальной эксплуатации программы,\n", "такие как отсутствующие файлы или ошибочные аргументы командной\n", "строки, должны обрабатываться другими средствами, например,\n", "посредством вывода сообщений об ошибках или записи сообщений в файл\n", "журнала.\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.2" } }, "nbformat": 4, "nbformat_minor": 4 }