2010-07-12 15 views
24

Tôi đang cố gắng tìm ra cách để có được tên của tất cả các trang trí trên một phương pháp. Tôi đã có thể nhận được tên phương thức và docstring, nhưng không thể tìm ra cách để có được một danh sách các trang trí.Introspection để có được tên trang trí trên một phương pháp?

+0

Điều này có vẻ không cần thiết. Bạn có nguồn. Có gì sai khi đọc nguồn? –

+15

@ S.Lott: bạn không thể trả lời cùng một cách về bất kỳ câu hỏi nào liên quan đến nội tâm? Tuy nhiên, nội tâm là hữu ích. –

+1

@ S.Lott: Không có gì sai khi đọc nguồn, giống như không có gì sai khi đọc nội dung của cơ sở dữ liệu trực tiếp thay vì sử dụng khung nhìn hoặc kịch bản, trừ khi tôi muốn tự động hóa. Tôi sử dụng trang trí để xác thực và tôi tạo báo cáo với các chế độ xem khác nhau để hiển thị những nhóm người dùng nào có quyền truy cập vào tài nguyên nào. Vì vậy, tôi cần truy cập có lập trình vào nguồn, giống như tôi cần truy cập có lập trình vào nguồn dữ liệu. – Tony

Trả lời

23

Nếu bạn có thể thay đổi cách bạn gọi trang trí từ

class Foo(object): 
    @many 
    @decorators 
    @here 
    def bar(self): 
     pass 

để

class Foo(object): 
    @register(many,decos,here) 
    def bar(self): 
     pass 

sau đó bạn có thể đăng ký trang trí theo cách này:

def register(*decorators): 
    def register_wrapper(func): 
     for deco in decorators[::-1]: 
      func=deco(func) 
     func._decorators=decorators   
     return func 
    return register_wrapper 

Ví dụ:

def many(f): 
    def wrapper(*args,**kwds): 
     return f(*args,**kwds) 
    return wrapper 

decos = here = many 

class Foo(object): 
    @register(many,decos,here) 
    def bar(self): 
     pass 

foo=Foo() 

Ở đây chúng ta truy cập vào tuple của trang trí:

print(foo.bar._decorators) 
# (<function many at 0xb76d9d14>, <function decos at 0xb76d9d4c>, <function here at 0xb76d9d84>) 

Ở đây chúng ta in chỉ là tên của các trang trí:

print([d.func_name for d in foo.bar._decorators]) 
# ['many', 'decos', 'here'] 
+1

Đây là một giải pháp tuyệt vời. : D Giả sử bạn có quyền truy cập vào mã chỉ định trang trí, mặc dù ... – Faisal

+0

Ok điều này có thể hoạt động, nhưng tại sao tôi không thể thêm mã func._whatever = 'something' vào trang trí hiện có của tôi và kiểm tra cho giá trị của thuộc tính _whatever khi thực hiện nội suy trên phương thức? – Tony

+0

Bạn có thể, nhưng sau đó bạn sẽ phải bẩn mỗi trang trí bạn viết với mối quan tâm chéo cắt bỏ các bài hát của nó đằng sau trong chức năng nó sửa đổi. – Faisal

0

Nó là không thể làm một cách tổng quát, bởi vì

@foo 
def bar ... 

là chính xác giống như

def bar ... 
bar = foo (bar) 

Bạn có thể làm điều đó trong một số trường hợp đặc biệt, như lẽ @staticmethod bằng cách phân tích đối tượng chức năng , nhưng không tốt hơn thế.

3

Đó là bởi vì trang trí là "đường cú pháp". Giả sử bạn có các trang trí sau:

def MyDecorator(func): 
    def transformed(*args): 
     print "Calling func " + func.__name__ 
     func() 
    return transformed 

Và bạn áp dụng nó cho một hàm:

@MyDecorator 
def thisFunction(): 
    print "Hello!" 

này tương đương với:

thisFunction = MyDecorator(thisFunction) 

Bạn có thể nhúng một "lịch sử" vào đối tượng chức năng, có lẽ, nếu bạn kiểm soát các trang trí. Tôi đặt cược có một số cách thông minh khác để làm điều này (có lẽ bằng cách gán trọng), nhưng tôi không phải là thạo trong Python không may. :(

1

Như Faisal notes, bạn có thể có những trang trí tự đính kèm siêu dữ liệu với chức năng, nhưng theo tôi biết nó không phải là thực hiện tự động.

4

Bạn không có thể, theo định nghĩa.Decorator:

@dec 
def foo(): 
    return 1 

chỉ là phím tắt cho:

def foo_internal() 
    return 1 
foo = dec(foo_internal) 

Chú ý rằng, trang trí dec chỉ đơn giản là một số điều gì đó có thể được gọi trở lại (chức năng, có thể một số đối tượng có thể được gọi khác). Bạn thậm chí không biết nếu foo có bất cứ điều gì để làm với nét trang trí, ví dụ:

def dec(f): 
    def not_foo(): 
     return 0 
    return not_foo 

Nếu bạn cần phải đặt thêm một số thông tin về phương pháp, các lớp học vv .--- như ví dụ thuộc tính trong .NET --- chỉ cần đặt một số thuộc tính trên chúng.

def foo() 
    return 1 
foo.decorated = True 

Hoặc triển khai trang trí đặt các thuộc tính đó, nếu nó thực sự giúp dễ đọc.

def dec(f): 
    f.decorated = True 
    return f 

Vâng. Python là mã nguồn mở. Bạn luôn có thể mở rộng trình thông dịch Python để theo dõi các trang trí được áp dụng cho đối tượng. Vì đối tượng trang trí (như được hiển thị ở trên) không phải liên quan đến bất kỳ cách nào để trang trí đối tượng, việc triển khai này sẽ phải lưu trữ thông tin từ đối tượng được trang trí trước khi áp dụng trang trí, áp dụng trang trí, thay thế thông tin trang trí trên đối tượng được trả lại và thêm thông tin về trang trí cuối cùng.

Có thể thực hiện. Nhưng tôi không chắc nó có thực sự hữu ích hay không. Và nó thêm một số chi phí cho mỗi trang trí. Vì vậy, tôi sẽ không mong đợi cơ chế như vậy trong Python chính thống, trừ khi nó cung cấp một số lợi ích khác, như đơn giản hóa việc thực hiện trang trí, v.v.

+2

"Bạn không thể, theo định nghĩa" có vẻ như một câu trả lời sai 100%, cho rằng Jaymon đã đăng cách thực hiện nó với gói phản ánh tiêu chuẩn ('kiểm tra nhập khẩu') ở trên. http://stackoverflow.com/a/31197273/1424877 Tôi đề nghị xóa câu trả lời này ... trừ khi tôi đang thiếu một cái gì đó tinh tế. – Quuxplusone

2

Bạn không thể, nhưng thậm chí tệ hơn là có thư viện để giúp che giấu thực tế là bạn có trang trí một chức năng để bắt đầu. Xem Functools hoặc thư viện trang trí (@decorator nếu tôi có thể tìm thấy nó) để biết thêm thông tin.

21

Tôi ngạc nhiên rằng câu hỏi này là quá cũ và không ai đã dành thời gian để thêm cách thực tế nội tâm để làm điều này, vì vậy ở đây là:

Mã bạn muốn kiểm tra ...

def template(func): 
    def wrapper(*args, **kwargs): 
     return func(*args, **kwargs) 
    return wrapper 

baz = template 
che = template 

class Foo(object): 
    @baz 
    @che 
    def bar(self): 
     pass 

Bây giờ bạn có thể kiểm tra lớp trên Foo với một cái gì đó như thế này ...

import ast 
import inspect 

def get_decorators(cls): 
    target = cls 
    decorators = {} 

    def visit_FunctionDef(node): 
     decorators[node.name] = [] 
     for n in node.decorator_list: 
      name = '' 
      if isinstance(n, ast.Call): 
       name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id 
      else: 
       name = n.attr if isinstance(n, ast.Attribute) else n.id 

      decorators[node.name].append(name) 

    node_iter = ast.NodeVisitor() 
    node_iter.visit_FunctionDef = visit_FunctionDef 
    node_iter.visit(ast.parse(inspect.getsource(target))) 
    return decorators 

print get_decorators(Foo) 

điều đó sẽ in một cái gì đó như thế này ...

{'bar': ['baz', 'che']} 

hoặc ít nhất nó đã làm khi tôi thử nghiệm này với Python 2.7.9 thật nhanh :)

+0

Được rồi, điều này có vấn đề trong python 3: (ít nhất nó có vẻ như, và tôi chắc chắn rằng tôi không * người tốt nhất với kiến ​​thức về kiểm tra/ast để bình luận với mức độ chắc chắn); về cơ bản tôi có một mã từ những gì bạn có ở trên, và 'inspect.getsource()' dường như trở lại với các khoảng trống ở phía trước 'def wrapper', sau đó đưa ra một lỗi thụt lề không mong muốn trên cuộc gọi' ast.parse' . – Jmons

+0

* CSONG * khi tôi cố gắng chứng minh điều này bằng cách sử dụng các công cụ chạy kịch bản trực tuyến hte (mà tôi nghĩ kịch bản thay vì chạy từ tệp), tôi nhận được 'OSError: mã nguồn không có sẵn' vì vậy tôi nghi ngờ có trường hợp (có lẽ bin chạy), nơi quá trình này sẽ không hoạt động. Có lẽ nó sẽ không hoạt động khi bin-chỉ chạy python tồn tại? – Jmons

+1

Phiên bản python3 nào? Tôi chỉ thử nghiệm nó trong python 2.7.13 và python 3.6.4 (đó là các phiên bản tôi có trên máy tính của tôi) và cả hai đều làm việc tốt. Ngoài ra, tôi không chắc chắn điều này sẽ làm việc ở khắp mọi nơi, nhưng nó làm việc ở khắp mọi nơi tôi đã cần nó. Tôi chắc chắn có thể thấy kịch bản chạy trực tuyến có bảo vệ cho các mô-đun như ast và kiểm tra, và có lẽ những thứ khác như mở tệp, vì nó là, theo định nghĩa, một môi trường có nhiều hơn. – Jaymon

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