В Python функцию можно
вызывать саму из себя. Это называется рекурсией. В качестве примера рекурсивной
функции я приведу вычисление числа в степени n (n – целое число).
def pow(x, n):
if n == 0:
return 1
else:
return x*pow(x, n-1)
И, далее, можно
ее вызвать так:
Теперь подробнее разберемся как она
работает. Для начала заметим, что
то есть, для
вычисления значения на текущем n-м шаге достаточно взять значение на
предыдущем n-1-м шаге и
умножить его на x. Эта формула, по сути, отражает принцип рекурсии. В
нашем случае она будет выглядеть так:
Далее, запуск
функции осуществляется с аргументами 2 и 3: pow(2, 3). Она
помещается в стек вызова функций, в котором хранится порядок вызова различных
функций. Далее, выполняется тело функции. Проверяется первое условие. Оно
оказывается ложным, так как 3 == 0 дает false. Поэтому идет
переход на else и прежде чем
выполнить оператор return, снова вызывается та же функция pow(2, 2).
Выполнение
функции pow(2, 3) останавливается,
в стек помещается вторая функция pow(2, 2) и она запускается. Снова
проверяется первое условие, оно ложно, переходим по else к оператору return и опять вызываем
функцию pow(2, 1).
Здесь снова все
повторяется, в стек помещается очередной вызов функции и по условию вызывается
следующая функция pow(2, 0).
Теперь первое
условие становится истинным и функция pow(2,0) возвращает значение 1 и
рекурсия не идет дальше вглубь – она останавливается. Функция pow(2.0)
завершается, она удаляется из стека вызовов и управление передается функции pow(2, 1). Но она
частично уже выполнена. Поэтому, берется значение 1 от pow(2, 0),
результат умножается на x=2 и величина 2 возвращается функцией pow(2, 1).
Функция pow(2,1) также
удаляется из стека, управление переходит к вышестоящей функции pow(2,2) и здесь мы
уже имеем результат умножения x*x, то есть, 2*2 =
4. Далее, возвращаемся к самой верхней функции pow(2,3), здесь 2*4=8
и этот результат становится результатом вычисления рекурсивной функции.
Функции с произвольным числом аргументов
Для начала
вспомним, что когда мы говорили о кортежах, то указывали на такую работу
оператора присваивания:
Здесь x и y принимают
значения 1 и 2 соответственно. Но когда этих значений было больше, чем
переменных:
то возникала
ошибка. Так вот, смотрите, если поставить оператор *, например, перед y, то ошибки не
возникнет:
и переменная x = 1, а y = [2, 3, 4]. То
есть, во вторую переменную будут записаны все оставшиеся значения в виде
списка. Или же, можно сделать так:
Тогда первые три
значения будут помещены в x:
x = [1, 2, 3]
а последнее в y: y = 4. То же
самое работает и со списками:
и со строками:
*x, y, z = "Hello world!"
И вообще с
любыми перечисляемыми типами данных. То есть, оператор * упаковывает оставшиеся
значения в список. Правда, мы не можем упаковывать уже упакованные данные,
например, так:
произойдет
ошибка, но вот так:
будет работать.
Этот же оператор
может выполнять и обратную операцию – распаковывать списки в набор данных. Смотрите,
предположим, мы описываем, что счетчик в цикле for должен пробегать
диапазон от -5 до 5 с шагом 1. Как вы уже знаете, это делается так:
for x in range(-5,6):
print(x, end=" ")
Далее, мы хотим
представить этот диапазон с помощью списка:
и передать его
функции range. Если записать
просто a:
То возникнет
ошибка, т.к. функция ожидает числа, а не список. Но, записав * перед переменной
a, произойдет
распаковка списка в набор из двух чисел: -5 и 6:
и программа
сработает без ошибок. Тогда как узнать: когда этот оператор распаковывает
данные, а когда запаковывает? Очень просто:
- Если
выполняется присвоение данных переменной с оператором *, то данные
упаковываются. Например, *y, x = 1,2,3.
- Если
выполняется передача списка с указанием оператора *, то происходит его распаковка.
Например, range(*[-5,6]).
Этот оператор
можно использовать для объявления функций с произвольным числом аргументов:
def myFunc(*args):
print(args)
и вызовем ее:
myFunc()
myFunc(1)
myFunc(1, 2)
myFunc(1, 3, "hello")
Смотрите, у нас args становится
кортежем с переданными значениями при вызове функции. Значит, мы спокойно можем
перебрать все значения кортежа, например, в цикле for:
def myFunc(*args):
for arg in args:
print(arg)
myFunc(1, 3, "hello")
В результате
получили функцию с произвольным числом аргументов. Где это может пригодиться?
Например, реализовать универсальную функцию для вычисления суммы переданных
элементов:
def mySum(*args):
S = 0
for arg in args:
S += arg
return S
print( mySum(1,2,3,4,5) )
А что если мы
хотим передавать функции неограниченное количество именованных аргументов? Вот
так:
myFunc(arg1=1, arg2=2, arg3=3)
В этом случае
аргумент у функции следует записать с двумя звездочками:
def myFunc(**kwargs):
print(kwargs)
И при ее вызове
мы видим, что kwargs – это словарь. Как
перебирать элементы словаря мы уже знаем, например, можно сделать так:
def myFunc(**kwargs):
for name, value in kwargs.items():
print(name, value)
и далее,
выполняем определенные действия в зависимости от значений именованных
аргументов.
Но, если мы
теперь попытаемся вызвать функцию вот так:
myFunc(1,2,3, arg1=1, arg2=2, arg3=3)
то возникнет
ошибка, т.к. первые три аргумента не именованные. Для такого варианта вызовов
функцию myFunc следует записать
так:
def myFunc(*args, **kwargs):
print(args)
print(kwargs)
Причем, вот эти
неименованные аргументы должны всегда следовать перед именованными. Или же
можно сделать так:
def myFunc(*args, sep="sep", end="end", **kwargs):
print(args)
print(kwargs)
print(sep, end)
или так:
def myFunc(x, y, *args, sep="sep", end="end", **kwargs):
print(args)
print(kwargs)
print(x, y)
print(sep, end)
Все эти
комбинации вполне допустимы, главное чтобы их порядок был именно таким: сначала
фактические параметры, затем необязательный список args, затем
формальные параметры и потом список этих параметров.
Анонимные или лямбда-функции
В Python существуют так
называемые анонимные функции (они же лямбда-функции). Что это такое? Давайте
для начала рассмотрим такой пример:
def showElements(lst, func):
for x in lst:
if func(x):
print(x)
def __odd(x):
return True if x%2 != 0 else False
a = [1,2,3,4,5,6,7]
showElements(a, __odd)
У нас имеется
функция showElements, которая
отображает из списка lst только те элементы, для которых функция
func возвращает
значение True. Далее, мы
создаем вспомогательную функцию __odd, которая возвращает True для нечетных
значений. И идет вызов showElements для списка a и селектора в
виде функции __odd.
Но что если мы
захотим изменить селектор и выбирать, например, все четные значения? Нужно
создавать новую функцию и затем указывать ссылку на нее? Это как то не очень
удобно. Вот здесь нам на помощь приходят лямбда-функции. Их можно объявить в
любом месте программы по следующему синтаксису:
lambda
arg1, arg2, …: выражение
Например, так:
и, затем,
вызвать ее:
То есть, в
анонимных функциях можно писать после двоеточий любой оператор, но только один.
Несколько нельзя. И кроме того, результат работы этого оператора автоматически
возвращается лямбда-функцией.
В нашем случае
мы можем записать такую функцию сразу в качестве второго аргумента:
showElements(a, lambda x: True if x%2==0 else False)
Видите, как это
удобно, понятно и наглядно. Мы сразу видим как будет выбирать значения функция showElements.
Если анонимная
функция не принимает никаких аргументов, то она записывается так:
p = lambda : "hello world"
p()
Вот такие
интересные и полезные возможности в объявлении функций существуют в языке Python.
Задания для самоподготовки
1. Написать
рекурсивную функцию для вычисления факториала числа n:
2. Написать
функцию для вычисления среднего арифметического переданных ей значений в виде
аргументов:
arg1, arg2, …, argN
3. Реализовать
функцию сортировки выбранных элементов по возрастанию: элементы передаются
функции в виде списка и выбираются они с помощью функции-селектора, указанной в
качестве второго параметра. Привести примеры вызова функции сортировки с
разными видами селекторов. Селекторы реализовать в виде лямбда-функций.