#python #recursion #decorator #python-decorators #functools
#python #рекурсия #декоратор #python-декораторы #функциональные средства
Вопрос:
Я пытаюсь понять пример последовательности Фибоначчи, используя декоратор для хранения значений уже вычисленных чисел. Например fib(5)
, будет вычислен, и когда мы доберемся до fib(6)
этого, он не будет вычисляться fib(5)
снова… Я немного разбираюсь в декораторах, но некоторые вещи меня просто смущают. У меня есть несколько вопросов о приведенном ниже коде.
from functools import wraps
def dec(func):
values = {}
@wraps(func)
def wrap(*args):
if args not in values:
values[args] = func(*args)
return values[args]
return wrap
@dec
def fib(n):
if n <= 2:
return 1
else:
return fib(n - 1) fib(n - 2)
- Почему
*args
используются вwrap()
? Разве не предполагается просто взять число n и проверить, есть ли его значение в словаре? Почемуargs
в некоторых местах вызываются с, а в некоторых без*
? - Что происходит, когда функция
fib
вызывается рекурсивно (как ведет себя функция декоратора). Сначала я подумал, что он вводит эту функцию во время каждой рекурсии, но это не может быть правильно, потому что словарь значений будет сброшен? Итак, затем он вводит толькоwrap()
функцию? - Почему он возвращает wrap в конце?
Ответ №1:
1- Вы совершенно правы. Нет необходимости в «*», так как вы проверяете только то значение, которое передается функции. Так что просто назовите это «n».
2- Сначала давайте выясним, что такое метка «fib» после того, как вы использовали «@dec» поверх нее? На самом деле теперь это ваша внутренняя функция внутри вашего декоратора (я имею в виду функцию «обертывания»). почему ? потому что @dec на самом деле делает это:
fib = dec(fib)
Итак, вызывается декоратор «dec», что он возвращает? функция «wrap». Что такое функция «wrap»? Это замыкание, которое имеет этот словарь «значений».
Всякий раз, когда вы вызываете свой декоратор, тело декоратора выполняется только один раз. Итак, существует только один словарь «значений». Что еще происходит во время выполнения тела декоратора «dec»? Ничего, кроме возврата ссылки на функцию «wrap». вот и все.
Теперь, когда вы вызываете свою функцию «fib» (первоначально функцию «wrap»), это закрытие выполняется нормально, поскольку это просто рекурсивная функция, за исключением того, что она обладает дополнительной функцией кэширования.
3- Потому что вам нужно иметь дескриптор внутренней функции (здесь функция «wrap»). Вы хотите вызвать его позже, чтобы вычислить Фибоначчи.
Ответ №2:
Что такое *args
*args
сопоставляет все оставшиеся аргументы в виде кортежа. Возьмем этот пример:
def f(*args):
print(args)
f('a', 'b')
# output: ('a', 'b')
В этом случае он используется для вызова внутренней функции с точно таким же аргументом, какими бы они ни были. Вы также можете использовать двойную звездочку для сопоставления аргументов ключевого слова, сейчас работают только позиционные аргументы.
Что происходит, когда fib вызывается рекурсивно
При оформлении функции с @
помощью, ссылка немедленно перезаписывается. При fib
вызовах fib()
внутри себя он сначала ищет переменную с этим именем в локальной области видимости. Поскольку их нет, они будут отображаться в следующей области, которая в данном случае является глобальной областью. Там он находит переменную с именем fib
, которая фактически назначается wrap
функции из вашего декоратора, с «контекстом» исходного fib
существа func
.
Посмотрите closures для получения дополнительной информации о том, как это работает.
Почему декоратор возвращается wrap
в конце
Декоратор в основном заменяет одну функцию другой. Он вызывает переменную после функции @
like a , а затем заменяет новую функцию, определенную def
с помощью, результатом этого вызова. В этом случае вы хотите заменить его wrap
новой функцией, которая может вызывать или не вызывать старую функцию.
Если вы ничего не возвращаете, переменной fib
просто будет присвоено значение None
(возвращаемое значение по умолчанию), и вы не сможете вызвать None
.
Ответ №3:
Вы можете получить хорошее представление о том, что здесь происходит, просто добавив несколько операторов печати, например:
from functools import wraps
def dec(func):
values = {}
@wraps(func)
def wrap(*args):
print("args: ", args, " *args:", *args, args not in values, values)
if args not in values:
values[args] = func(*args)
return values[args]
print("Wrap", wrap)
return wrap
@dec
def fib(n):
if n <= 2:
return 1
else:
return fib(n - 1) fib(n - 2)
print("Answer", fib(5))
Итак, результат этого:
Wrap <function fib at 0x7facac4b70d0>
args: (5,) *args: 5 True {}
args: (4,) *args: 4 True {}
args: (3,) *args: 3 True {}
args: (2,) *args: 2 True {}
args: (1,) *args: 1 True {(2,): 1}
args: (2,) *args: 2 False {(2,): 1, (1,): 1, (3,): 2}
args: (3,) *args: 3 False {(2,): 1, (1,): 1, (3,): 2, (4,): 3}
Answer 5
Чтобы начать с последней части вашего вопроса, как вы можете видеть из выходных данных, функция завершается в начале программы, когда Python выполняет весь код до того, как он перейдет к print
оператору. Это происходит только один раз, и именно это позволяет вашему последующему вызову fib
вызывать уже обернутую функцию.
Для того, чтобы функция-оболочка работала, ей необходимо знать, какие аргументы передаются обернутой функции, и она делает это, *args
чтобы увидеть переданный параметр, который ей нужен для запоминания его и его результата.
Разница между args
и *args
сводится к распаковке кортежей. Вы можете видеть из приведенного выше вывода, который args
содержит, например (1, )
, и *args
распаковывает это в just 1
.
Итак, используя args
in обернутую функцию, она фактически сохраняет числа не как ключи в values
словаре, как вы могли бы подозревать, а кортеж, содержащий число. В этом случае можно было бы выполнить распаковку, но это был бы ненужный дополнительный шаг.
Это также дает вам хорошее представление о том, как происходит рекурсия, как при первом вызове fib(n)
, первая часть оператора return return fib(n - 1)
, так что это должно быть оценено перед fib(n-2)
, так что это немедленно приводит к запоминанию каждого значения от n до 1, а затем вы возвращаете обратностек рекурсии, оценка fib(n-2)
, но все эти результаты могут быть values
удовлетворены без дальнейших рекурсивных вызовов fib
.