Декоратор для рекурсии
У меня есть декоратор, подсчитывающий количество вызовов рекурсивной функции (ncalls) и глубину рекурсии (rdepth)
Для не рекурсивной функции значения будут (1,1)
На примерах, которые придумала я, и на примерах из контекста работает правильно, но при сдаче в контексте не проходит один тест, но я никак не могу придумать ситуацию, которая бы сломала мой код.
counter.flag и counter.w (не зануляется только когда вышли из функции столько же раз, как глубоко заходили) нужны чтобы понимать, когда мы в данную функцию заходим первый раз и когда нужно сбросить счетчики, counter.d считает глубину рекурсии в текущий момент
def counter(func):
counter.flag = True
counter.w = 0
counter.d = 0
@functools.wraps(func)
def count_f(*args, **kwargs):
if counter.flag:
count_f.ncalls = 0
count_f.rdepth = 0
counter.flag = False
counter.w = 0
counter.d = 0
count_f.ncalls += 1
counter.d += 1
counter.w = 0
if count_f.rdepth < counter.d:
count_f.rdepth = counter.d
result = func(*args, **kwargs)
counter.d -= 1
counter.w += 1
if counter.w == count_f.rdepth:
counter.flag = True
return result
return count_f
Подскажите, пожалуйста, что здесь можно сделать?
Пример
@counter
def fib(n):
if n == 0 or n == 1:
return n
else:
return fib(n - 2) + fib(n - 1)
fib(3)
print(fib.ncalls, fib.rdepth)
# 5 3
Пример из контеста:
@counter
def func2(n, steps):
if steps == 0:
return
func2(n + 1, steps - 1)
func2(n - 1, steps - 1)
if __name__ == "__main__":
func2(0, 5)
print(func2.ncalls, func2.rdepth)
func2(0, 3)
print(func2.ncalls, func2.rdepth)
# 63 6
# 15 4
Ответы (1 шт):
Не привязывайтесь к декоратору, храните все в декорированной функции.
def counter(f):
def reset():
wrapped.rdepth = 0
wrapped.ncalls = 0
def wrapped(*args, **kwargs):
if wrapped.depth == 0:
reset()
wrapped.depth += 1
wrapped.ncalls += 1
wrapped.rdepth = max(wrapped.rdepth, wrapped.depth)
try:
return f(*args, **kwargs)
finally:
wrapped.depth -= 1
wrapped.depth = 0
reset()
return wrapped
или так, если не хотите хранить лишние поля на виду
def counter(f):
def reset():
wrapped.rdepth = 0
wrapped.ncalls = 0
def wrapped(*args, **kwargs):
nonlocal depth
if depth == 0:
reset()
depth += 1
wrapped.ncalls += 1
wrapped.rdepth = max(wrapped.rdepth, depth)
try:
return f(*args, **kwargs)
finally:
depth -= 1
depth = 0
reset()
return wrapped
try / finally обновит глубину, даже если при вызове декорируемой функции произойдет ошибка.
@counter
def f():
raise ValueError
try:
f()
except ValueError:
print(f.rdepth, f.ncalls) # 1 1
try:
f()
except ValueError:
print(f.rdepth, f.ncalls) # 1 1 с try / finally и 2 2 без