{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Функции и модули" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этой лекции мы расскажем о том, что такое функции в языках программирования и для чего они нужны. Также мы рассмотрим понятия модуля и пакета, обсудим концепцию пространств имен и вкратце познакомим вас с парадигмой процедурного программирования." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Содержание лекции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* [Функции](#Функции)\n", " * [Создание и вызов](#Создание-и-вызов)\n", " * [Аргументы и возвращаемое значение](#Аргументы-и-возвращаемое-значение)\n", " * [Рекурсивные функции](#Рекурсивные-функции)\n", " * [Лямбда-функции](#Лямбда-функции)\n", "* [Модули](#Модули)\n", " * [Импортирование модуля](#Импортирование-модуля)\n", " * [Пакеты](#Пакеты)\n", "* [Пространства имен](#Пространства-имен)\n", "* [Процедурное программирование](#Процедурное-программирование)\n", "* [Вопросы для самоконтроля](#Вопросы-для-самоконтроля)\n", "* [Задание](#Задание)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Функции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Функция**, также иногда называемая *подпрограммой* или *процедурой* - это фрагмент исходного кода (как правило, именованный), к которому можно обратиться из других частей программы. Можно говорить о том, что функция представляет собой маленькую законченную программу внутри основной программы (поэтому ее иногда и называют подпрограммой). Большинство функций имеют имя для того, чтобы к ним было удобно обращаться из остальных частей программы. По сути, понятие функции тесно связано с понятием переменной, только первая связывает некоторое имя с блоком исходного кода, а вторая - со значением в памяти (числовым, строковым и т.д.)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Создание и вызов" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Функции в языке программирования Python создаются с помощью инструкции `def`:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n",
"def function_name(parameters_list):\n",
" code_block\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Имя функции *function_name* должно быть [допустимым идентификатором](04_Data_Types.ipynb#Допустимые-идентификаторы) в языке Python. Поскольку функция как правило выполняет некоторое действие, то в ее имени рекомендуется использовать глаголы. В качестве примера можно привести такие имена функций: `send_message`, `get_inverted_value`, `make_job` и т.д."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Функция может иметь **параметры**, описываемые в *parameters_list*, который представляет собой 0 или более переменных, разделенных запятыми. Эти переменные \"видны\" **только** внутри функции (в ее *code_block*) так, словно они были созданы в ней явно с помощью инструкций присваивания, и к ним нельзя обратится из других частей исходного кода."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Компонент *code_block* представляет собой последовательность произвольных инструкций языка программирования Python, в том числе других инструкций `def`. Обратите внимание, что все инструкции в *code_block* должны иметь отступ относительно инструкции `def`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"В любом месте внутри *code_block* функции можно использовать специальную инструкцию `return` вместе со значением произвольного типа, с помощью которой функция может вернуть результат своей работы."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Функции используются следующим образом:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"1. В начале функция создается с помощью инструкции `def`. Не любую последовательность инструкций имеет смысл делать функцией - как правило она должна делать небольшую и четко определенную работу, необходимость в которой может возникать в разных частях исходного кода основной программы. Процесс создания функции, а также соответствующая инструкция `def` в исходном коде, часто называются **определением функции**.\n",
"2. После того, как функция определена, ее можно использовать в других частях программы для того, чтобы выполнить работу, реализованную в функции. Для этого в нужных местах исходного кода пишут имя функции и в скобках `()` указывают значения для ее параметров. Это называется **вызовом функции**. Переменные и литералы, указанные в скобках при вызове функции называются ее **аргументами**.\n",
"3. Когда интерпретатор встречает инструкцию вызова функции, он вначале инициализирует с помощью аргументов ее параметры, а затем выполняет *code_block* функции. Можно представлять себе, что при вызове функции интерпретатор как бы вставляет в исходный код инструкции из *code_block*, при этом заменив в них имена параметров на имена аргументов.\n",
"4. Если в *code_block* функции выполняется инструкция `return`, интерпретатор берет значение, указанное в ней, вставляет его в место вызова функции (говорят, что функция **возвращает** значение), а затем продолжает выполнять программу с этой точки. Можно представлять себе это так, словно значение из инструкции `return` заменило собой инструкцию вызова функции. В `return` можно и не указывать никакого значения - в этом случае возвращается специальное значение `None`.\n",
"5. Если инструкция `return` не встретилась, то интерпретатор обрабатывает *code_block* функции целиком, а затем выполняет действия, аналогичные тому, как если бы в конце *code_block* была инструкция `return` без значения."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"В большинстве примеров в данной лекции мы используем функцию `print` для вывода результатов работы наших программ на экран. Эта функция определена в самом языке Python и в качестве аргументов принимает список литералов, переменных или выражений, чьи значения затем выводит на экран, отделяя их пробелом друг от друга. Количество аргументов в вызове функции `print`, а также их типы данных, может быть любым:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"test 10 10.3 True 30\n"
]
}
],
"source": [
"a = 10\n",
"b = True\n",
"\n",
"# выводим 5 аргументов: строку, целое число, число с плавающей точкой,\n",
"# булевое значение и значение арифметического выражения\n",
"print('test', a, 10.3, b, a * 2 + 10) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Познакомившись с функцией `print`, попробуем создать и вызвать нашу собственную функцию."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"hello, Alice\n",
"hello, Bob\n"
]
}
],
"source": [
"# определение функции\n",
"\n",
"def print_hello(name): # name - это параметр функции, который \"виден\" только в ее code_block\n",
" print('hello,', name) # это code_block функции, только в нем мы можем обращаться к параметру name\n",
"\n",
"# основная программа\n",
"\n",
"name1 = 'Alice'\n",
"name2 = 'Bob'\n",
"\n",
"print_hello(name1) # вызов функции print_hello, name1 - это аргумент, который присваивается параметру name\n",
"print_hello(name2) # вызов функции print_hello с другим аргументом (name2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Определение функции должно находится в исходном коде до ее вызовов. Это ограничение связано с тем, что интерпретатор выполняет исходный код последовательно сверху вниз, поэтому в момент вызова функции должен знать, какой блок кода связан с ней. В следующем пример интерпретатор генерирует исключение `NameError`, так как в момент вызова функции он еще не обрабатывал ее определение:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'print_hello_world' is not defined",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m\n",
"lambda parameters_list: expression\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Как и для обычных функций, *parameters_list* представляет собой 0 или более переменных, разделенных запятыми. Для них можно указывать значение по умолчанию по тем же правилам, что и для обычных функций."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Expression* представляет собой некоторое выражение языка программирования Python, например, арифметическое, логическое или условное."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Приведем простой пример лямбда-функции, которая возвращает модуль числа:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5\n",
"5\n"
]
}
],
"source": [
"abs_value = lambda x: x if x >= 0 else -x # используем условное выражение в качестве лямбда-функции\n",
"print(abs_value(5))\n",
"print(abs_value(-5))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Рассмотрим последний пример из предыдущего раздела. Хорошая функция должна представлять собой законченную небольшую программу, которая может многократно использоваться в других участках кода (подробнее об этом читайте ниже в разделе [Процедурное программирование](#Процедурное-программирование)). Сказать это о функциях `decorate_text` мы не можем - они используются лишь как параметры для функции `print_hello`, и вряд ли понадобятся где-либо еще. Поэтому этот пример лучше переписать с использованием лямбда-функций:"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"---hello, Alice---\n"
]
}
],
"source": [
"# обратите внимание на то, как мы используем лямбда-функцию в качестве значения по умолчанию\n",
"def print_hello(name, decorator=lambda text: '---' + text + '---'):\n",
" hello_text = 'hello, ' + name\n",
" hello_text = decorator(hello_text)\n",
" print(hello_text)\n",
"\n",
"print_hello('Alice')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Если нам потребуется любой другой способ декорирования текста, мы можем указать его прямо при вызове функции `print_hello`:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"***hello, Bob***\n",
"===hello, Cooper===\n"
]
}
],
"source": [
"print_hello('Bob', lambda text: '***' + text + '***')\n",
"print_hello('Cooper', lambda text: '===' + text + '===')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Модули"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Перед тем, как мы начнем изучать модули, давайте посмотрим, как еще можно создать программу на языке Python, не используя среду разработки Jupyter Notebook. На самом деле, все что нам нужно - это любой текстовый редактор и интерпретатор Python. При установке [дистрибутива Anaconda](02_Installing_Python.ipynb#Описание-дистрибутива) интерпретатор уже был установлен, а в качестве текстового редактора можно использовать стандартный блокнот в ОС Windows. Запустим его и напишем в нем простейшую программу:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Теперь сохраним этот файл и запомним путь к нему. По общепринятому соглашению, файл с исходным кодом на языке Python должен иметь расширение *.py*, поэтому в качестве имени используем, например, *hello.py*."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Для того, чтобы выполнить эту программу, нужно запустить интерпретатор Python и передать ему в командной строке наш файл *hello.py*. Для начала однако давайте убедимся, что интерпретатор Python присутствует в нашей системе. Для этого запустим командную строку Windows и выполним команду *\"python --version\"*. Вы должны увидеть примерно следующее:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Если вместо этого было выведено сообщение о том, что \"python\" является неизвестной командой, то возможно одно из двух: либо интерпретатор отсутствует в системе, либо путь к нему не прописан в специальной переменной среды *Path*. Если вы правильно установили дистрибутив Anaconda, никаких ошибок быть не должно."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Последним шагом является вызов интерпретатора и передача ему в командной строке нашего файла с программой. Для удобства, мы перейдем в папку, куда был сохранен файл *hello.py* (в нашем случае - *c:/python*), чтобы в дальнейшем не нужно было писать полный путь до него:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"В принципе, можно сказать, что мы уже создали наш первый **модуль**, потому что любой файл с расширением *.py*, содержащий исходный код на языке Python, может им считаться. Однако, чтобы быть полезным, модуль обычно содержит определенный набор пременных, функций и различных сложных типов данных (о которых пойдет речь в лекции, посвященной классам), предназначенных для решения определенного класса задач. Такие модули затем можно использовать в своих программах и получать доступ к функциональности, реализованной в них. При этом говорят, что модуль **экспортирует** функциональность (константы, функции и т.д.), а программа, которая использует модуль, **импортирует** ее."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Существует огромное количество модулей для языка Python. Некоторые из них являются частью так называемой **стандартной библиотеки** Python, реализованной создателями языка и состоящей из множества модулей, предоставляющих средства для работы с операционной системой и файлами, сетевыми протоколами, криптографическими алгоритмами и многое другое. Другие реализуются сторонними разработчиками для узкого класса задач. Обилие существующих модулей является сильной стороной языка Python: необходимость реализовывать какой-нибудь сложный алгоритм возникает очень редко, потому что практически всегда удается найти модуль, в котором эта функциональность уже присутствует, и просто использовать его."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Давайте создадим собственный модуль, на этот раз более полезный, чем предыдущий *hello.py*. Мы поместим в него функции для вычисления площади некоторых геометрических фигур, а также константу $\\pi$."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"PI = 3.141592\n",
"\n",
"def calc_triangle_area(a, b, c):\n",
" p = (a + b + c) / 2\n",
" return (p * (p - a) * (p - b) * (p - c)) ** 0.5\n",
"\n",
"def calc_rectangle_area(a, b):\n",
" return a * b\n",
"\n",
"def calc_circle_area(r):\n",
" return PI * (r ** 2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Чтобы вынести этот код в отдельный модуль, создадим в блокноте файл *area.py* и скопируем код туда."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Импортирование модуля"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Для того, чтобы получить доступ к функциям из модуля `area` его нужно **импортировать** в нашу программу с помощью инструкции `import` (мы уже встречались с ней раньше, когда импортировали модуль decimal из стандратной библиотеки Python). Заметим, что все инструкции `import` рекомендуется размещать в самом начале программы."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"ename": "ModuleNotFoundError",
"evalue": "No module named 'area'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m