Functions and recursion - Learn Python 3 - Snakify

Lesson 8
Функции и рекурсия


1. функции

Напомним, что в математике факториал числа n определяется как n! = 1 ⋅ 2 ⋅ ... ⋅ n (как произведение всех целых чисел от 1 до n). Например, 5! = 1 ⋅ 2 ⋅ 3 ⋅ 4 ⋅ 5 = 120. Ясно, что факториал легко вычислить, используя цикл for. Представьте, что нам нужно в нашей программе несколько раз вычислить факториал разных чисел (или в разных местах кода). Конечно, вы можете написать вычисление факториала один раз, а затем с помощью Copy-Paste вставить его туда, где вам это нужно:

# вычислить 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 , который обозначен звездочкой.
Advertising by Google, may be based on your interests

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)
Advertising by Google, may be based on your interests

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()
Две наиболее распространенные причины, вызывающие бесконечную рекурсию:
  1. Неправильное условие остановки. Например, если в факториал-калькуляционной программе мы забываем поставить проверку, if n == 0 , то factorial(0) вызовет factorial(-1) , который вызовет factorial(-2) и т. Д.
  2. Рекурсивный вызов с неправильными параметрами. Например, если функция factorial(n) вызывает функцию factorial(n) , мы также получим бесконечную цепочку вызовов.

Поэтому при кодировании рекурсивной функции сначала необходимо убедиться, что она достигнет своих условий остановки - подумать, почему ваша рекурсия когда-либо закончится.

Advertising by Google, may be based on your interests