{ "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": [ "![Блокнот](./images/07/notepad.png)" ] }, { "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": [ "![Версия Python](./images/07/python-version.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если вместо этого было выведено сообщение о том, что \"python\" является неизвестной командой, то возможно одно из двух: либо интерпретатор отсутствует в системе, либо путь к нему не прописан в специальной переменной среды *Path*. Если вы правильно установили дистрибутив Anaconda, никаких ошибок быть не должно." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Последним шагом является вызов интерпретатора и передача ему в командной строке нашего файла с программой. Для удобства, мы перейдем в папку, куда был сохранен файл *hello.py* (в нашем случае - *c:/python*), чтобы в дальнейшем не нужно было писать полный путь до него:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![Выполнение программы](./images/07/hello.png)" ] }, { "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