2012-05-25 2 views
7

장식 된 재귀 함수가 어떻게 작동하는지 이해하는 데 어려움을 겪고 있습니다. 다음 니펫 :파이썬에서 재귀 함수 장식하기

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 

첫번째 인쇄의 F (n)은 너무 자연스럽게 재귀 호출 '원본'마다 F (n)를 출력한다.

두 번째 것은 def_f (n)을 인쇄하므로 n이 래퍼로 전달되면 f (n)을 재귀 적으로 호출합니다. 그러나 래퍼 자체는 재귀 적이 지 않으므로 '장식 됨'이 하나만 인쇄됩니다.

제 3의 퍼즐은 제게 데코레이터 @dec를 사용하는 것과 같습니다. 왜 장식 된 f (n)도 래퍼를 5 번 호출합니까? def_f = dec (f)와 f = dec (f)는 두 개의 동일한 함수 객체에 바인딩 된 단지 두 개의 키워드라고 봅니다. 데코 레이팅 된 기능에 장식되지 않은 것과 동일한 이름이 지정되면 다른 작업이 진행되고 있습니까?

감사합니다.

+1

원래'f' 기능에 대한 참조가 여전히 존재 내부의 래퍼 내부 func.__name__f.__name__를 인쇄하여 볼 수 있습니다. 'f = dec (f)'하면, 항상 새로운 함수를 호출 할 것이다. 그리고 새로운 기능은 원본을 호출합니다. – JBernardo

+0

'decorator'는 실제로이 함수에 데코레이터를 적용하지 않기 때문에 여기에서 사용할 올바른 용어가 아닐 수도 있습니다. 'f = dec (f)'의 마지막 테스트는 거의 (정확하게는 아닐지라도)'@dec def f'와 거의 같다. –

답변

4

당신이 말했듯이 첫 번째 것은 보통 때와 같이 호출됩니다.

두 번째 버전에서는 dec_f라는 데코 레이팅 된 버전을 전역 범위에 넣습니다. Dec_f가 호출되면 "Decorated!"가 인쇄되지만 dec에 전달 된 f 함수 안에는 dec_f가 아닌 f 자체가 호출됩니다. 이름 f는 전역 범위에서 검색되고 발견되며, 래퍼가 없어도 여전히 정의되어 있으므로 f 만 호출됩니다.

3 개의 예에서 데코 레이팅 된 버전을 이름 f에 지정하면 함수 f 내부에서 이름 f가 조회 될 때 전역 범위에서 f를 찾습니다. 이제는 데코 레이팅 된 버전입니다. 데코레이터 프롤로그/에필로그가 다른 함수 이전 또는 이후에 하나을 수행 할 수있는 것으로 밝혀지면

+0

고마워! 이것이 제가 찾고 있던 것입니다. 그래서 문제는 def f의 return (f (n-1) + n) 문장입니다. f (n-1)은 이제 새로운 dec (f)입니다. – jianglai

5

파이썬에서의 모든 할당은 객체에 대한 바인딩 이름 일뿐입니다. 당신이

f = dec(f) 

있을 때 무슨 일을하는 것은 dec(f)의 반환 값에 이름 f 바인딩입니다. 이 시점에서 f은 더 이상 원래 함수를 참조하지 않습니다. 원래 함수가 여전히 존재하고 새 f에 의해 호출되었지만 더 이상 원래 함수에 대한이라는 이름의 이 없습니다.

1

함수는 f을 호출하는데,이 함수는 파이썬이 엔클 로징 범위를 검색합니다.

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) 

는 생산 : 생산 어떤

r = timing(fibonacci)(5) 
print "Fibonacci %d of 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 

는 하나의 프롤로그/에필로그를 강제로

0

(210) 나는이 일이 여기에 더 명확 비트 만들 것이라고 생각 코드를 조금

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 개체를 닫습니다. 그래서 wrapper 내부에서 func를 호출 할 때마다 원래의 f가 호출되지만, f 내의 재귀 호출은 f로 장식 된 버전을 호출합니다.

당신은 실제로 간단하여 하나가 호출되는 함수 f