Как использовать параметры с значениями по умолчанию в рекурсивных функциях?

Параметры со значениями по умолчанию в рекурсивных функциях используются так же, как и в обычных.

Важно помнить, что значение по умолчанию вычисляется только один раз, при определении функции.

Для изменяемых типов данных (списки, словари) это может привести к неожиданному поведению, если вы изменяете значение по умолчанию в процессе рекурсии. В таком случае лучше использовать None в качестве значения по умолчанию и инициализировать параметр внутри функции.

Пример:


def рекурсивная_функция(n, аккумулятор=None):
    if аккумулятор is None:
        аккумулятор = []
    
    аккумулятор.append(n)
    
    if n > 0:
        return рекурсивная_функция(n - 1, аккумулятор)
    else:
        return аккумулятор

print(рекурсивная_функция(3)) # Вывод: [3, 2, 1, 0]
  

Использование параметров со значениями по умолчанию в рекурсивных функциях ничем принципиально не отличается от их использования в обычных функциях. Главное понимать, как и когда эти значения по умолчанию инициализируются.

Принцип работы: Значения по умолчанию вычисляются один раз, когда функция определена, а не при каждом вызове. Это означает, что если значение по умолчанию является изменяемым объектом (например, списком или словарем), его изменения будут сохраняться между рекурсивными вызовами, если вы явно не будете его копировать.

Пример 1: Простой случай (неизменяемый тип данных):


def factorial(n, accumulator=1):
    """
    Рекурсивная функция для вычисления факториала.
    accumulator - параметр с значением по умолчанию, который сохраняет промежуточный результат.
    """
    if n == 0:
        return accumulator
    else:
        return factorial(n-1, n * accumulator)

print(factorial(5))  # Выведет 120
  

В этом примере accumulator - это целое число (неизменяемый тип данных). Каждый рекурсивный вызов получает новое значение accumulator, и значение по умолчанию (1) используется только при самом первом вызове функции, если не передать другое значение явно.

Пример 2: Осторожно с изменяемыми типами данных:


def append_numbers(n, lst=[]):
  """
  Рекурсивно добавляет числа от 1 до n в список.
  ВНИМАНИЕ: lst - список, изменяемый тип данных.
  """
  if n > 0:
    append_numbers(n - 1, lst)
    lst.append(n)
  return lst

print(append_numbers(3)) # Выведет [1, 2, 3]
print(append_numbers(5)) # Выведет [1, 2, 3, 1, 2, 3, 4, 5] -  неожиданный результат!
  

В этом примере значение по умолчанию lst=[] создается только один раз при определении функции. Когда append_numbers(3) добавляет элементы в список, этот же список используется и при следующем вызове append_numbers(5). Поэтому мы видим старые значения в списке.

Решение для изменяемых типов данных: Используйте None в качестве значения по умолчанию и создавайте новый экземпляр объекта внутри функции, если параметр не был передан.


def append_numbers_correct(n, lst=None):
  """
  Рекурсивно добавляет числа от 1 до n в список.
  Правильное использование изменяемого типа данных.
  """
  if lst is None:
    lst = []

  if n > 0:
    append_numbers_correct(n - 1, lst)
    lst.append(n)
  return lst

print(append_numbers_correct(3)) # Выведет [1, 2, 3]
print(append_numbers_correct(5)) # Выведет [1, 2, 3, 4, 5] - теперь правильно!
  

В этом примере, если lst не передан, он инициализируется новым пустым списком. Это обеспечивает независимость между вызовами функции.

Вывод:

  • Использование значений по умолчанию в рекурсивных функциях аналогично обычным функциям.
  • Необходимо быть внимательным при использовании изменяемых типов данных в качестве значений по умолчанию, чтобы избежать нежелательного сохранения состояния между рекурсивными вызовами.
  • Рекомендуется использовать None в качестве значения по умолчанию для изменяемых типов данных и создавать новый экземпляр объекта внутри функции, если параметр не передан.
0