2012-05-25 48 views
7

Tôi gặp khó khăn trong việc hiểu cách hoạt động của hàm đệ quy được trang trí. Đối với đoạn mã sau:Chức năng đệ quy trang trí trong 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)) 

Đầu ra là:

(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 

Đầu tiên một bản in f (n) để tự nhiên nó in mỗi f thời gian 'gốc' (n) được gọi là đệ quy.

Bản thứ hai in def_f (n), vì vậy khi n được chuyển tới trình bao bọc, nó gọi f (n) đệ quy. Nhưng bản thân trình bao bọc không được đệ quy nên chỉ có một 'Trang trí' được in.

Câu hỏi thứ ba giải thích tôi, giống như sử dụng trang trí @dec. Tại sao trang trí f (n) gọi các wrapper năm lần cũng? Dường như với tôi rằng def_f = dec (f) và f = dec (f) chỉ là hai từ khóa liên kết với hai đối tượng hàm giống nhau. Có cái gì khác xảy ra khi chức năng trang trí được đặt cùng tên với cái chưa được trang trí?

Cảm ơn!

+1

tham chiếu với bản gốc 'chức năng f' vẫn còn tồn tại, vì vậy mà một được gọi. Khi bạn thực hiện 'f = dec (f)', bạn sẽ luôn gọi hàm mới. Và hàm mới sẽ gọi ban đầu. – JBernardo

+0

'decorator' có thể không phải là thuật ngữ thích hợp để sử dụng ở đây, vì bạn không bao giờ thực sự áp dụng một trình trang trí cho hàm. Bài kiểm tra cuối cùng của bạn về 'f = dec (f)' gần như (nếu không chính xác) giống như '@dec def f' –

Trả lời

4

Như bạn đã nói, cái đầu tiên được gọi là bình thường.

tùy chọn thứ hai đặt phiên bản trang trí của f được gọi là dec_f trong phạm vi toàn cầu. Dec_f được gọi, để in "Trang trí!", Nhưng bên trong hàm f được chuyển đến tháng mười hai, bạn gọi f chính nó, không phải là dec_f. tên f được tra cứu và tìm thấy trong phạm vi toàn cục, nơi nó vẫn được định nghĩa mà không có trình bao bọc, do đó, từ trên, chỉ f được gọi.

trong ví dụ 3re, bạn chỉ định phiên bản trang trí cho tên f, vì vậy khi bên trong hàm f, tên f được tra cứu, tìm trong phạm vi toàn cục, tìm f, hiện là phiên bản trang trí.

+0

Cảm ơn! Đây là những gì tôi đang tìm kiếm. Vì vậy, vấn đề là câu lệnh return (f (n-1) + n) trong def f, trong đó f (n-1) là dec (f) mới bây giờ. – jianglai

5

Tất cả nhiệm vụ trong Python chỉ là các tên liên kết với các đối tượng. Khi bạn có

f = dec(f) 

những gì bạn đang làm là gắn tên f với giá trị trở lại của dec(f). Tại thời điểm đó, f không còn đề cập đến chức năng ban đầu. Hàm ban đầu vẫn tồn tại và được gọi bởi số f mới, nhưng bạn không có một tham chiếu được đặt tên vào hàm ban đầu nữa.

1

Chức năng của bạn gọi ra một cái gì đó gọi là f, mà python nhìn lên trong phạm vi kèm theo.

Cho đến khi tuyên bố f = dec(f), f vẫn bị ràng buộc với chức năng chưa được mở, vì vậy đó là những gì được gọi.

0

Nếu trang trí cho thấy một đoạn mở đầu/bạt để được thực hiện một trước hoặc sau khi chức năng khác, chúng tôi có thể tránh làm việc đó nhiều lần trang trí mô phỏng với các chức năng đệ quy.

Ví dụ:

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) 

Tạo:

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 

Chúng ta có thể mô phỏng các trang trí để buộc chỉ có một mở đầu/bạt như:

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

nào sản xuất:

0.000010 seconds 
Fibonacci 5 of is 5 
0

Thay đổi mã của bạn một chút

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)) 

Tôi nghĩ rằng điều này sẽ làm cho mọi việc cắn rõ ràng hơn ở đây, hàm wrapper thực sự đóng đối tượng func từ kèm theo phạm vi. Vì vậy, mỗi cuộc gọi đến func wrapper bên trong sẽ gọi f ban đầu nhưng các cuộc gọi đệ quy trong f sẽ gọi phiên bản trang trí của f.

Bạn thực sự có thể thấy điều này bằng cách đơn giản in func.__name__ bên wrapper và f.__name__ bên trong hàm f

Các vấn đề liên quan