2010-08-25 35 views
5

[Cập nhật]: Trả lời inline bên dưới câu hỏiDecorator thay đổi trạng thái chức năng từ phương pháp thực hiện chức năng

tôi có một chương trình thanh tra và một mục tiêu là cho logic trong một trang trí để biết xem chức năng nó là trang trí là một phương pháp học hay chức năng thông thường. Điều này đang thất bại một cách kỳ lạ. Dưới đây là mã chạy bằng Python 2.6:

def decorate(f): 
    print 'decorator thinks function is', f 
    return f 

class Test(object): 
    @decorate 
    def test_call(self): 
     pass 

if __name__ == '__main__': 
    Test().test_call() 
    print 'main thinks function is', Test().test_call 

Sau đó, trên thực:

decorator thinks function is <function test_call at 0x10041cd70> 
main thinks function is <bound method Test.test_call of <__main__.Test object at 0x100425a90>> 

Bất kỳ manh mối về những gì đang xảy ra sai, và nếu nó là có thể cho @decorate để suy ra một cách chính xác test_call đó là một phương pháp?

[Trả lời] câu trả lời của carl bên dưới gần như hoàn hảo. Tôi đã có một vấn đề khi sử dụng trang trí trên một phương thức mà lớp con gọi. Tôi đã điều chỉnh mã của mình để bao gồm so sánh im_func trên các thành viên của lớp cha:

ismethod = False 
for item in inspect.getmro(type(args[0])): 
    for x in inspect.getmembers(item): 
     if 'im_func' in dir(x[1]): 
      ismethod = x[1].im_func == newf 
      if ismethod: 
       break 
    else: 
     continue 
    break 

Trả lời

5

Như những người khác đã nói, một chức năng được trang trí trước khi nó bị ràng buộc, vì vậy bạn không thể trực tiếp xác định xem đó là một 'phương pháp' hay 'chức năng'.

Một cách hợp lý để xác định xem hàm có phải là phương pháp hay không là kiểm tra xem 'tự' có phải là tham số đầu tiên hay không. Trong khi không đơn giản, hầu hết các mã Python tuân thủ Công ước này:

import inspect 
ismethod = inspect.getargspec(method).args[0] == 'self' 

Đây là một cách phức tạp mà dường như để tự động tìm hiểu xem phương pháp này là một ràng buộc hay không. Làm việc cho một vài trường hợp đơn giản trên CPython 2.6, nhưng không có lời hứa. Nó quyết định một hàm là một phương thức nếu đối số đầu tiên là một đối tượng có hàm được trang trí ràng buộc với nó.

import inspect 

def decorate(f): 
    def detect(*args, **kwargs): 
     try: 
      members = inspect.getmembers(args[0]) 
      members = (x[1].im_func for x in members if 'im_func' in dir(x[1])) 
      ismethod = detect in members 
     except: 
      ismethod = False 
     print ismethod 

     return f(*args, **kwargs) 
    return detect 

@decorate 
def foo(): 
    pass 

class bar(object): 
    @decorate 
    def baz(self): 
     pass 

foo() # prints False 
bar().baz() # prints True 
+0

+1 vì tôi yêu hackery và bạn đánh tôi với nó. – aaronasterling

1

Trình trang trí của bạn được chạy trước khi hàm trở thành phương thức. def từ khóa bên trong một lớp xác định một dòng chức năng ở bất kỳ nơi nào khác, sau đó các hàm được định nghĩa trong phần thân của một lớp được thêm vào lớp làm phương thức. Decorator hoạt động trên hàm trước khi nó được xử lý bởi lớp đó là lý do tại sao mã của bạn 'không thành công'.

Không có cách nào cho @decorate để xem hàm thực sự là một phương thức. Một giải pháp cho điều đó là để trang trí chức năng cho dù đó là gì (ví dụ: thêm thuộc tính do_something_about_me_if_I_am_a_method;-)) và sau đó xử lý lại sau khi lớp được tính toán (lặp qua các thành viên của lớp và làm bất cứ điều gì bạn muốn với trang trí đó).

+0

Chúng thực sự được lưu trữ trong lớp '__dict__' làm hàm - hãy thử và xem! Chúng chỉ bị ràng buộc khi bạn truy cập chúng. Nhưng có, không có cách nào để làm điều đó =). – katrielalex

+0

Điều đó là có thể. Tôi phải đã sử dụng getattr() để truy cập chúng, sau đó. –

3

Không, điều này là không thể nếu bạn đã yêu cầu, vì không có sự khác biệt vốn có giữa phương pháp và chức năng bị ràng buộc. Một phương thức chỉ đơn giản là một hàm được bao bọc để lấy cá thể gọi là đối số đầu tiên (sử dụng Python descriptors).

Một cuộc gọi như:

Test.test_call 

mà trả về một phương pháp ràng buộc, dịch để

Test.__dict__[ 'test_call' ].__get__(None, spam) 

mà là một phương pháp cởi ra, mặc dù

Test.__dict__[ 'test_call' ] 

là một hàm.Điều này là do các hàm là các bộ mô tả có phương thức trả về phương thức __get__; khi Python thấy một trong các chuỗi này trong chuỗi tra cứu, nó gọi phương thức __get__ thay vì tiếp tục chuỗi.

Thực tế, 'phương pháp liên kết' của một hàm được xác định trong thời gian chạy, không phải tại thời gian xác định!

Trình trang trí đơn giản nhìn thấy hàm như được xác định, mà không cần tra cứu nó trong một __dict__, vì vậy không thể biết liệu nó đang xem xét một phương pháp ràng buộc.


thể có thể làm điều này với một trang trí lớp học mà sửa đổi __getattribute__, nhưng đó là một hack tranh bóng ác ý. Tại sao bạn phải có chức năng này? Chắc chắn, vì bạn phải đặt trình trang trí trên chính hàm đó, bạn có thể truyền cho nó một đối số cho biết liệu hàm được nói có được định nghĩa trong một lớp không?

class Test: 
    @decorate(method = True) 
    def test_call: 
     ... 

@decorate(method = False) 
def test_call: 
    ... 
+0

+1. Đề xuất cuối cùng của bạn có lẽ là cách tiếp cận tốt nhất ở đây. – aaronasterling

0

Tôi đã thử một ví dụ hơi khác, với một phương pháp được trang trí và một phương pháp chưa được trang trí.

def decorate(f): 
    print 'decorator thinks function is', f 
    return f 

class Test(object): 
    @decorate 
    def test_call(self): 
    pass 
    def test_call_2(self): 
    pass 

if __name__ == '__main__': 
    print 'main thinks function is', Test.test_call 
    print 'main thinks function 2 is', Test.test_call_2 

Sau đó, đầu ra là:

decorator thinks function is <function test_call at 0x100426b18> 
main thinks function is <unbound method Test.test_call> 
main thinks function 2 is <unbound method Test.test_call_2> 

Như vậy, trang trí nhìn thấy một loại khác với chức năng chính đã làm, nhưng trang trí không thay đổi kiểu của hàm, hoặc nó sẽ khác nhau từ chức năng undecorated.

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