Функции
Работа любой компьютерной программы — это выполнение процессором большого набора элементарных инструкций. В машинном коде, с которым работает процессор, все команды очень простые:
Считать из оперативной памяти одно целое число в специальную ячейку
Прибавить к одному числу значение другой ячейки
Сравнить ячейку с нулем
Вернуться на пару команд назад и пр.
Команды машинного кода не могут вывести окошко программы или проиграть аудиофайл, не могут посчитать среднюю оценку в классе или загрузить страничку из Интернета. Машинный код не умеет полноценно работать даже с обычными строками или списками и не может выполнять сложные математические расчеты. Однако программа целиком все это делает, потому что состоит из множества команд, которые в комбинации дают нужный эффект.
Многие из команд Python, которые вы уже знаете, требуют от процессора выполнения десятков команд. Если бы программист писал их вручную, даже простейшие программы — вроде наших учебных заданий — создавались бы несколько дней. При этом даже опытному программисту было бы очень легко допустить ошибку.
В качестве мотивирующего примера рассмотрим программу, в которой последовательно запрашивается имя, а затем выводится приветствие по имени для трех живых существ:
Программа небольшая, но уже видно много проблем:
Во-первых, три раза приходится повторять фактически одно и то же
Во-вторых, приходится вводить разные имена переменных, чтобы ничего не перепутать и не поприветствовать кого-нибудь неправильным именем
В-третьих, если вы захотите исправить приветствие на более официальное — например, «Здравствуйте», вам придется внести одинаковые исправления сразу в трех разных местах
Даже в такой маленькой программе можно при исправлении допустить опечатку. А представьте, что фраза используется десять раз в разных местах большой программы — тогда придется искать каждое приветствие и исправлять его.
Было бы здорово иметь возможность устранить дублирование кода: в каком-то одном месте сообщить интерпретатору, что именно мы понимаем под словом «поприветствовать», а затем попросить интерпретатор использовать определение термина «поприветствовать» там, где мы его попросим об этом.
Итак, сформулируем, чего мы хотим добиться:
Один раз определить, что значит «поприветствовать», т. е. сгруппировать и поименовать повторяющийся кусок кода
Многократно в дальнейшем «ссылаться» на это определение везде, где нам только потребуется
Замечательно, что язык Python действительно обладает такими выразительными возможностями — функциями.
Функция — особым образом сгруппированный набор команд, которые выполняются последовательно, но воспринимаются как единое целое. При этом функция может возвращать (или не возвращать) свой результат.
Для того чтобы использовать какую-нибудь собственную функцию, вначале необходимо ее объявить, т. е. рассказать, что именно она будет делать. В нашем примере мы объявим функцию greet
с помощью ключевого слова def
, после которого идет название нашей функции, скобки, двоеточие, а затем на двух последующих строчках — описание того, что она собой представляет:
Обратите внимание: это описание расположено в блоке кода с отступом.
Далее мы можем использовать нашу функцию greet
всякий раз, когда в нашем коде возникает необходимость кого-нибудь поприветствовать:
Обращение к ранее объявленной функции с целью выполнения ее команд называется вызовом. В нашем примере функция greet
один раз объявляется, а затем три раза вызывается. Что мы получили в результате:
Код сократился и стал понятнее. Теперь нам не нужно выискивать, где какая переменная заводится, где и для чего она используется. Функция сама говорит, что она делает: greet — «поприветствовать».
Нам не приходится заводить несколько разных переменных.
Чтобы поменять приветствие во всей программе, достаточно изменить одну строчку.
Итак, функции нужны, чтобы группировать команды, а заодно — чтобы не писать один и тот же код несколько раз.
Например, достаточно один раз написать функцию greet
и потом пользоваться ею постоянно. Польза от этого особенно очевидна, когда функция действительно сложная и используется много раз в разных местах программы. Например, загрузку данных из Интернета или отрисовку персонажа компьютерной игры удобно оформлять в виде отдельных функций.
Еще одна важная вещь состоит в том, что функции имеют имена.
Благодаря им программу можно сделать понятной не только компьютеру, но и человеку. Тут все так же, как с именами переменных: если переменная имеет ничего не говорящее название, сложно угадать, что в ней хранится. Если участок кода не сгруппирован в функцию, иногда приходится буквально дешифрировать, для чего он нужен в программе. А если он оформлен в виде функции, название функции само подскажет, что делает этот код.
Проиллюстрируем сказанное на примере. Попробуйте угададать, что делает такой код:
После некоторых нетривиальных усилий по дешифровке можно понять, что этот код вычисляет среднее, минимальное и максимальное значение списка:
А вот тот же самый код, но переработанный с помощью встроенных функций и хороших названий переменных:
Какой вариант вам нравится больше?
На самом деле, когда программист думает о том, что должна делать программа, он обычно представляет ее как раз в форме функций. Мы обычно не говорим, какие действия должен выполнить алгоритм, а описываем, что мы хотим получить. Например, мы хотим посчитать среднегодовую температуру, значит, нам нужна функция вычисления среднего значения из набора чисел.
Определение простейших функций
Давайте подытожим то, что мы знаем о функциях.
У каждой функции есть заголовок (его обычно называют сигнатурой) и тело.
Сигнатура описывает, как функцию вызывать, а тело описывает, что эта функция делает.
Сигнатура содержит имя функции и аргументы (то есть параметры), которые передаются в функцию.
Записывается это так:
Тело функции, как и в операторе if
или в операторе цикла, обязательно идет с отступом. Это нужно, чтобы интерпретатор Python знал, где заканчивается код функции. Заодно это здорово помогает структурировать программу. Даже в языках, где отступы не требуются, их все равно принято писать, чтобы упростить чтение программы.
Давайте напишем еще одну совсем простую функцию из одной единственной команды, которая просто выводит на экран приветствие.
Теперь, чтобы поприветствовать пользователя, вам достаточно в основной программе написать: simple_greetings()
. Это называется « вызвать функцию».
Обратите внимание: у этой функции нет аргументов ни в определении, ни при вызове. Однако пустые скобочки после названия функции писать все равно нужно.
Функцию, как и переменную, необходимо сначала объявить, и только потом использовать. Поэтому следующая программа выдаст вам ошибку name ’simple_greetings2′ is not defined
.
Впрочем, от такой функции проку не очень много: она не сокращает количество кода и не сильно упрощает понимание происходящего в программе. Поэтому в виде функции стоит оформлять только логически законченный блок кода, особенно если он необходим в нескольких местах программы.
После функции до кода, который находится вне функции, необходимо делать отступ в две пустые строки для повышения читаемости кода. Если у вас есть несколько функций в одном файле, между кодом одной и сигнатурой другой функции тоже надо оставлять две пустые строки.
Начальные знания о локальных переменных
В тот момент, когда вы вызываете функцию greet
, начинают выполняться команды, написанные в теле функции. Когда работа функции доходит до конца, исполнение программы продолжается со строки, которая вызывала функцию. Мы еще посмотрим на этот процесс подробнее с помощью отладчика.
Обратите внимание: теперь в программе используется только одна переменная — name. Как же так? Ведь мы договорились, что не будем использовать одну и ту же переменную для разных имен? На самом деле мы не используем одну и ту же переменную. При каждом вызове функции эта переменная создается заново, а в конце работы функции — прекращает свое существование. Это очень важный момент.
Область видимости переменной
Снаружи функции greet
переменная name вообще не существует. Таким образом, функция очерчивает тот участок программы, где переменная нужна и используется. Этот участок, в котором переменная живет, называется областью видимости переменной (по-английски — scope
).
Благодаря ограничению области видимости переменной программисту не нужно беспокоиться, не «всплывет» ли эта переменная в другом месте программы. Изменяя переменную внутри функции, программист понимает, что он может что-то испортить только внутри функции, но не поломает работу остальной программы. Можно сказать, что вся работа с переменной локализована, т.е. сосредоточена внутри функции.
Локальные и глобальные переменные
Переменные, создаваемые внутри функций, недоступны извне и существуют только внутри функции. Они называются локальными.
Создаваемые вне функции переменные могут быть доступны из функций. Они являются глобальными.
По возможности избегайте использования глобальных переменных для предотвращения конфликтов. О локальных переменных и областях видимости мы поговорим намного подробнее на следующих уроках.
Аргументы функций
Мы рассмотрели функции, которые выполняют всякий раз одни и те же действия. Это бывает полезно, но все же большая часть программ требует выполнения немного разных действий.
Например, функция print
(а это именно функция) должна каждый раз выводить на экран разные сообщения — в зависимости от переданных аргументов.
Аргументы (параметры) могут изменять поведение функции. Например, функция len принимает строки или списки (и другие коллекции). В зависимости от конкретного аргумента она возвращает разный результат, а значит, выполняет внутри немного различные действия.
Рассмотрим функцию, которая должна выводить на экран содержимое списка, печатая каждый элемент на своей строчке. Вряд ли нам захочется заводить функцию, которая раз за разом выводит содержимое одного и того же списка. Скорее, нужна функция, которая может распечатать любой список. Конкретный список мы передаем функции в качестве параметра при ее вызове. Функция же работает с тем, что ей передали. Выглядит это так:
При первом вызове функции print_array
переменная array будет равна ['Hello', 'world']
. При втором вызове переменная array будет равна [123, 456, 789]
.
Разберемся, в каком порядке выполняется код при вызове функций. В примере:
ничего удивительного не происходит — списки складываются, а затем передаются в функцию.
Давайте рассмотрим более сложный пример:
Аргументы в функции print_hello
никак не используются, но сейчас это неважно. Рассмотрим, в каком порядке выполняются функции.
В момент вызова функции ей необходимо передать вычисленные аргументы. Если аргументы не вычислены, они вычисляются слева направо.
В данном случае функция print_hello
принимает аргумент arg_1
, который является значением функции print_comrade
(по умолчанию — None
), и аргумент arg_2
, который является значением функции print_petrov
. Таким образом, сначала выполнится функция print_comrade
, затем print_petrov
и лишь в самом конце print_hello
. Результатом работы программы будет напечатанный текст: