2012-05-25 6 views
7

Мне сложно понять, как работает декорированная рекурсивная функция. Для следующего фрагмента кода:Оформление рекурсивных функций в python

def dec(f): 
    def wrapper(*argv): 
     print(argv, 'Decorated!') 
     return(f(*argv)) 
    return(wrapper) 

def f(n): 
    print(n, 'Original!') 
    if n == 1: return(1) 
    else: return(f(n - 1) + n) 

print(f(5)) 
print 

dec_f = dec(f) 
print(dec_f(5)) 
print 

f = dec(f) 
print(f(5)) 

Выход:

(5, 'Original!') 
(4, 'Original!') 
(3, 'Original!') 
(2, 'Original!') 
(1, 'Original!') 
15 

((5,), 'Decorated!') 
(5, 'Original!') 
(4, 'Original!') 
(3, 'Original!') 
(2, 'Original!') 
(1, 'Original!') 
15 

((5,), 'Decorated!') 
(5, 'Original!') 
((4,), 'Decorated!') 
(4, 'Original!') 
((3,), 'Decorated!') 
(3, 'Original!') 
((2,), 'Decorated!') 
(2, 'Original!') 
((1,), 'Decorated!') 
(1, 'Original!') 
15 

Первый принты е (п), поэтому, естественно, он печатает «оригинал» каждый раз, когда п (п) называется рекурсивно.

Второй печатает def_f (n), поэтому, когда n передается в оболочку, он вызывает f (n) рекурсивно. Но сама оболочка не рекурсивна, поэтому печатается только один «Украшенный».

Третий меня озадачивает, что аналогично использованию декоратора @dec. Почему украшенный f (n) также вызывает обертку пять раз? Мне кажется, что def_f = dec (f) и f = dec (f) - это всего лишь два ключевых слова, привязанных к двум идентичным объектам функции. Есть ли что-то еще, когда украшенной функции присваивается то же имя, что и декорированный?

Спасибо!

+1

ссылка на оригинальный 'f' функции по-прежнему существует, таким образом, что одна называется. Когда вы выполняете 'f = dec (f)', вы всегда будете вызывать новую функцию. И новая функция вызовет оригинал. – JBernardo

+0

'decorator' может не быть подходящим термином для использования здесь, так как вы никогда не применяете декоратор к функции. Ваш последний тест 'f = dec (f)' почти (если не точно) совпадает с '@dec def f' –

ответ

4

Как вы сказали, первый называется как обычно.

второй поместил декорированную версию f, названную dec_f в глобальном масштабе. Dec_f вызывается так, что печатает «Decorated!», Но внутри функции f, переданной dec, вы вызываете сам f, а не dec_f. имя f просматривается и обнаруживается в глобальной области, где оно все еще определено без оболочки, поэтому из-за него вызывается только f.

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

+0

Спасибо! Это то, что я искал. Таким образом, проблема заключается в выражении return (f (n-1) + n) в def f, где f (n-1) - новый dec (f). – jianglai

5

Все назначение в Python - это просто привязка имен к объектам. Если у вас есть

f = dec(f) 

то, что вы делаете, связывая имя f для возвращаемого значения dec(f). В этот момент f больше не относится к исходной функции. Исходная функция все еще существует и вызывается новым f, но у вас больше нет с именем.

1

Ваша функция вызывает что-то, называемое f, которое python просматривает в охватывающей области.

До тех пор пока заявление f = dec(f), f все еще связано с развернутой функцией, так это то, что вызывается.

0

Если декораторы указать пролог/эпилог сделать один до или после того, как другой функции, мы можем избежать делать это несколько раз имитирующих декораторов с рекурсивными функциями.

Например:

def timing(f): 
    def wrapper(*args): 
     t1 = time.clock(); 
     r = apply(f,args) 
     t2 = time.clock(); 
     print"%f seconds" % (t2-t1) 
     return r 
    return wrapper 

@timing 
def fibonacci(n): 
    if n==1 or n==2: 
     return 1 
    return fibonacci(n-1)+fibonacci(n-2) 

r = fibonacci(5) 
print "Fibonacci of %d is %d" % (5,r) 

Производит:

0.000000 seconds 
0.000001 seconds 
0.000026 seconds 
0.000001 seconds 
0.000030 seconds 
0.000000 seconds 
0.000001 seconds 
0.000007 seconds 
0.000045 seconds 
Fibonacci of 5 is 5 

Мы можем моделировать декоратора, чтобы заставить только один пролог/эпилог, как:

r = timing(fibonacci)(5) 
print "Fibonacci %d of is %d" % (5,r) 

Который производит:

0.000010 seconds 
Fibonacci 5 of is 5 
0

изменили ваш код немного

def dec(func): 
    def wrapper(*argv): 
     print(argv, 'Decorated!') 
     return(func(*argv)) 
    return(wrapper) 

def f(n): 
    print(n, 'Original!') 
    if n == 1: return(1) 
    else: return(f(n - 1) + n) 

print(f(5)) 
print 

dec_f = dec(f) 
print(dec_f(5)) 
print 

f = dec(f) 
print(f(5)) 

Я думаю, что это будет делать вещи бит более ясным, то функция упаковщик фактически закрывает объект Func из вмещающих сферы. Поэтому каждый вызов функции func внутри обертки вызовет исходный f, но рекурсивный вызов внутри f вызовет декорированную версию f.

Вы можете увидеть это, просто печатая func.__name__ внутри обертки и f.__name__ внутри функции f

Смежные вопросы