Lesson 8
Функции и рекурсия
1. функции
Напомним, что в математике факториал числа n определяется как
# вычислить 3! res = 1 for i in range(1, 4): res *= i print(res) # вычислить 5! res = 1 for i in range(1, 6): res *= i print(res)
Однако, если мы допустим ошибку в исходном коде, этот ошибочный код появится во всех местах, где мы скопировали вычисление факториала. Более того, код длиннее, чем может быть. Чтобы избежать повторной записи той же логики в языках программирования, существуют функции.
Функции - это секции кода, которые изолированы от остальной части программы и выполняются только при вызове. Вы уже встретили функции sqrt()
, len()
и print()
. Все они имеют что-то общее: они могут принимать параметры (ноль, один или несколько из них), и они могут возвращать значение (хотя они могут и не возвращаться). Например, функция sqrt()
принимает один параметр и возвращает значение (квадратный корень из заданного числа). Функция print()
может принимать различное количество аргументов и ничего не возвращает.
Теперь мы хотим показать вам, как написать функцию factorial()
которая принимает один параметр - число и возвращает значение - факториал этого числа.
def factorial(n): res = 1 for i in range(1, n + 1): res *= i return res print(factorial(3)) print(factorial(5))
Мы хотим дать несколько объяснений. Во-первых, код функции должен быть помещен в начало программы (до места, где мы хотим использовать функцию factorial()
, если быть точным). Первая строка def factorial(n):
из этого примера является описанием нашей функции; слово factorial является идентификатором (имя нашей функции). Сразу после идентификатора появляется список параметров, которые получает наша функция (в скобках). Список состоит из разделенных запятыми идентификаторов параметров; в нашем случае список состоит из одного параметра n
. В конце строки поместите двоеточие.
Затем идет тело функции. В Python тело должно быть отступом (по Tab или четыре пробела, как всегда). Эта функция вычисляет значение n! и сохраняет его в переменной res
. Последней строкой функции является return res
, которая выходит из функции и возвращает значение переменной res
.
Оператор return
может отображаться в любом месте функции. Его выполнение завершает функцию и возвращает указанное значение в место, где была вызвана функция. Если функция не возвращает значение, оператор return фактически не возвращает какое-либо значение (хотя оно все еще может быть использовано). Некоторым функциям не нужно возвращать значения, и оператор return может быть опущен для них.
Мы хотели бы привести еще один пример. Вот функция max()
которая принимает два числа и возвращает максимум из них (на самом деле эта функция уже стала частью синтаксиса Python).
def max(a, b): if a > b: return a else: return b print(max(3, 5)) print(max(5, 3)) print(max(int(input()), int(input())))
Теперь вы можете написать функцию max3()
которая принимает три числа и возвращает максимум из них.
def max(a, b): if a > b: return a else: return b def max3(a, b, c): return max(max(a, b), c) print(max3(3, 5, 4))
Встроенная функция max()
в Python может принимать различное количество аргументов и возвращать максимум из них. Вот пример того, как можно написать такую функцию.
def max(*a): res = a[0] for val in a[1:]: if val > res: res = val return res print(max(3, 5, 4))
a
, который обозначен звездочкой.
2. Локальные и глобальные переменные
Внутри функции вы можете использовать переменные, объявленные где-то вне нее:
def f(): print(a) a = 1 f()
Здесь переменная a
установлена в 1, а функция f()
печатает это значение, несмотря на то, что когда мы объявляем функцию f
эта переменная не инициализируется. Причина в том, что во время вызова функции f()
(последняя строка) переменная a
уже имеет значение. Вот почему функция f()
может отображать ее.
Такие переменные (объявленные вне функции, но доступные внутри функции) называются глобальными .
Но если вы инициализируете некоторую переменную внутри функции, вы не сможете использовать эту переменную за ее пределами. Например:
def f(): a = 1 f() print(a)
Мы получаем ошибку NameError: name 'a' is not defined
. Такие переменные, объявленные внутри функции, называются локальными . После выхода из функции они становятся недоступными.
Что действительно очаровательно здесь, что произойдет, если вы измените значение глобальной переменной внутри функции:
def f(): a = 1 print(a) a = 0 f() print(a)
Эта программа напечатает вам числа 1 и 0. Несмотря на то, что значение переменной a
измененное внутри функции, вне функции остается неизменным! Это делается для того, чтобы «защитить» глобальные переменные от непреднамеренных изменений функции. Итак, если какая-либо переменная изменяется внутри функции, переменная становится локальной переменной, и ее модификация не изменит глобальную переменную с тем же именем.
Более формально: интерпретатор Python рассматривает переменную локальную для функции, если в коде этой функции есть хотя бы одна команда, которая изменяет значение переменной. Тогда эта переменная также не может быть использована до инициализации. Инструкции, которые изменяют значение переменной - operator =
, +=
и использование переменной в качестве цикла for
параметра. Однако, даже если оператор change-variable никогда не выполняется, интерпретатор не может его проверить, и переменная остается локальной. Пример:
def f(): print(a) if False: a = 0 a = 1 f()
Произошла ошибка: UnboundLocalError: local variable 'a' referenced before assignment
. А именно, в функции f()
идентификатор a
становится локальной переменной, так как функция содержит команду, которая модифицирует переменную a
. Инструкция по модификации никогда не будет выполнена, но интерпретатор не проверяет ее. Поэтому, когда вы пытаетесь распечатать переменную a
, вы обращаетесь к неинициализированной локальной переменной.
Если вы хотите, чтобы функция могла изменять какую-либо переменную, вы должны объявить эту переменную внутри функции, используя ключевое слово global
:
def f(): global a a = 1 print(a) a = 0 f() print(a)
В этом примере будет напечатан вывод 1 1, потому что переменная a
объявлена глобальной, а ее изменение внутри функции вызывает ее изменение в глобальном масштабе.
Однако лучше не изменять значения глобальных переменных внутри функции. Если ваша функция должна изменить некоторую переменную, пусть она вернет это значение, и вы выберете при вызове функции явно назначить переменную этому значению. Если вы следуете этим правилам, логика функций работает независимо от логики кода, и поэтому такие функции могут быть легко скопированы из одной программы в другую, экономя ваше время.
Например, предположим, что ваша программа должна вычислить факториал данного номера, который вы хотите сохранить в переменной f. Вот как вы не должны этого делать:
def factorial(n): global f res = 1 for i in range(2, n + 1): res *= i f = res n = int(input()) factorial(n) print(f) # делать другие вещи с переменной f
Это пример плохого кода, потому что трудно использовать другое время. Если завтра вам понадобится другая программа для использования функции «factorial», вы не сможете просто скопировать эту функцию и вставить новую программу. Вам нужно будет убедиться, что эта программа не содержит переменную f
.
Гораздо лучше переписать этот пример следующим образом:
# начало фрагмента кода, который можно скопировать из программы в программу def factorial(n): res = 1 for i in range(2, n + 1): res *= i return res # конец фрагмента кода n = int(input()) f = factorial(n) print(f) # делать другие вещи с переменной f
Полезно сказать, что функции могут возвращать более одного значения. Вот пример возврата списка из двух или более значений:
return [a, b]
Вы можете вызвать функцию такого списка и использовать его в нескольких назначениях:
n, m = f(a, b)
3. Рекурсия
Как мы видели выше, функция может вызвать другую функцию. Но функции могут также называть себя! Чтобы проиллюстрировать это, рассмотрим пример факторно-вычислительной функции. Хорошо известно, что 0! = 1, 1! = 1. Как рассчитать значение n! для больших n? Если бы нам удалось вычислить значение (n-1) !, то мы легко вычислим n !, так как n! = N⋅ (n-1) !. Но как вычислить (n-1) !? Если мы вычислили (n-2) !, тогда (n-1)! = (N-1) ⋅ (n-2) !. Как рассчитать (n-2) !? Если ... В итоге мы получаем 0 !, что равно 1. Таким образом, для вычисления факториала мы можем использовать значение факториала для меньшего целого числа. Этот расчет можно сделать с помощью Python:
def factorial(n): if n == 0: return 1 else: return n * factorial(n - 1) print(factorial(5))
Ситуация, когда функция вызывает себя, называется рекурсией , и такая функция называется рекурсивной.
Рекурсивные функции - мощный механизм программирования. К сожалению, они не всегда эффективны и часто приводят к ошибкам. Наиболее распространенной ошибкой является бесконечная рекурсия , когда цепочка вызовов функций никогда не заканчивается (ну, на самом деле, она заканчивается, когда у вас заканчивается свободная память на вашем компьютере). Пример бесконечной рекурсии:
def f(): return f()Две наиболее распространенные причины, вызывающие бесконечную рекурсию:
- Неправильное условие остановки. Например, если в факториал-калькуляционной программе мы забываем поставить проверку,
if n == 0
, тоfactorial(0)
вызоветfactorial(-1)
, который вызоветfactorial(-2)
и т. Д. - Рекурсивный вызов с неправильными параметрами. Например, если функция
factorial(n)
вызывает функциюfactorial(n)
, мы также получим бесконечную цепочку вызовов.
Поэтому при кодировании рекурсивной функции сначала необходимо убедиться, что она достигнет своих условий остановки - подумать, почему ваша рекурсия когда-либо закончится.