2012-01-09 24 views
12

Tôi đang viết một trang trí và vì nhiều lý do gây phiền nhiễu khác nhau, hãy kiểm tra xem hàm đang gói có được định nghĩa độc lập hay là một phần của lớp hay không subclassing).Python: một người trang trí có thể xác định xem một hàm đang được định nghĩa bên trong một lớp không?

Ví dụ:

def my_decorator(f): 
    defined_in_class = ?? 
    print "%r: %s" %(f, defined_in_class) 

@my_decorator 
def foo(): pass 

class Bar(object): 
    @my_decorator 
    def bar(self): pass 

nên in:

<function foo …>: False 
<function bar …>: True 

Ngoài ra, xin lưu ý:

  • Tại trang trí điểm được áp dụng chức năng vẫn sẽ là một chức năng, không phải một phương pháp không liên kết, vì vậy việc kiểm tra phương pháp ví dụ/không liên kết (sử dụng typeof hoặc inspect) sẽ không t làm việc.
  • Vui lòng chỉ cung cấp các đề xuất giải quyết vấn đề này - Tôi biết rằng có nhiều cách tương tự để thực hiện kết thúc này (ví dụ: sử dụng trình trang trí lớp học), nhưng tôi muốn chúng xuất hiện ở thời điểm trang trí không muộn hơn.

[0]: cụ thể, tôi viết một trình trang trí giúp bạn dễ dàng thực hiện kiểm tra tham số với nose. Tuy nhiên, nose sẽ không chạy trình phát thử nghiệm trên các lớp con unittest.TestCase, vì vậy tôi muốn trang trí của tôi có thể xác định xem nó được sử dụng bên trong lớp con của TestCase và không có lỗi thích hợp. Giải pháp rõ ràng - sử dụng isinstance(self, TestCase) trước khi gọi chức năng gói không hoạt động, bởi vì chức năng được gói cần làm máy phát, không được thực hiện tại tất cả.

+0

Đối với những người tò mò, đây là kết quả: http://paste.pocoo.org/show/532430/ –

Trả lời

11

Hãy xem đầu ra của inspect.stack() khi bạn quấn một phương thức. Khi thực thi trang trí của bạn đang được tiến hành, khung ngăn xếp hiện tại là lệnh gọi hàm cho người trang trí của bạn; khung ngăn xếp tiếp theo là hành động gói @ đang được áp dụng cho phương pháp mới; và khung thứ ba sẽ là bản thân định nghĩa lớp, nó có một khung ngăn xếp riêng biệt vì định nghĩa lớp là không gian tên riêng của nó (được bao bọc để tạo ra một lớp khi nó được thực hiện xong).

Tôi đề nghị, do đó:

defined_in_class = (len(frames) > 2 and 
        frames[2][4][0].strip().startswith('class ')) 

Nếu tất cả các chỉ số điên trông unmaintainable, sau đó bạn có thể rõ ràng hơn bằng cách lấy khung ngoài mảnh bằng mảnh, như thế này:

import inspect 
frames = inspect.stack() 
defined_in_class = False 
if len(frames) > 2: 
    maybe_class_frame = frames[2] 
    statement_list = maybe_class_frame[4] 
    first_statment = statement_list[0] 
    if first_statment.strip().startswith('class '): 
     defined_in_class = True 

Lưu ý rằng tôi làm không xem bất kỳ cách nào để hỏi Python về tên lớp hoặc hệ thống phân cấp thừa kế tại thời điểm trình bao bọc của bạn chạy; điểm đó là "quá sớm" trong các bước xử lý, vì việc tạo lớp chưa được hoàn thành. Hoặc là phân tích dòng bắt đầu bằng chính số class của bạn và sau đó nhìn vào hình cầu của khung đó để tìm siêu lớp, hoặc người khác chọc xung quanh đối tượng mã frames[1] để xem bạn có thể học được gì - có vẻ như tên lớp sẽ tăng lên là frames[1][0].f_code.co_name trong mã ở trên , nhưng tôi không thể tìm thấy bất kỳ cách nào để tìm hiểu những gì lớp siêu lớp sẽ được đính kèm khi tạo lớp kết thúc.

-2

Tôi nghĩ rằng các chức năng trong module inspect sẽ làm những gì bạn muốn, đặc biệt là isfunctionismethod:

>>> import inspect 
>>> def foo(): pass 
... 
>>> inspect.isfunction(foo) 
True 
>>> inspect.ismethod(foo) 
False 
>>> class C(object): 
...  def foo(self): 
...    pass 
... 
>>> inspect.isfunction(C.foo) 
False 
>>> inspect.ismethod(C.foo) 
True 
>>> inspect.isfunction(C().foo) 
False 
>>> inspect.ismethod(C().foo) 
True 

Sau đó bạn có thể làm theo các Types and Members table để truy cập các chức năng bên trong ràng buộc hoặc cởi ra phương pháp:

>>> C.foo.im_func 
<function foo at 0x1062dfaa0> 
>>> inspect.isfunction(C.foo.im_func) 
True 
>>> inspect.ismethod(C.foo.im_func) 
False 
+0

Tôi hy vọng rằng 'kiểm tra' sẽ được tham gia, nhưng không phải là cách bạn đã mô tả. Xem ghi chú đầu tiên của tôi (rằng tại thời điểm trang trí được áp dụng hàm chỉ là một thể hiện của 'hàm', không phải là' unboundmethod' hoặc 'boundmethod'). –

+0

Ông đã không nói rõ ràng, nhưng quan điểm của ông là điều này không hoạt động ở thời gian định nghĩa lớp. –

+0

Có, bạn nói đúng - Tôi đã cập nhật câu hỏi của mình để rõ ràng hơn. Tôi đánh giá cao câu trả lời kỹ lưỡng, nó không giải quyết được câu hỏi của tôi. –

2

Một số giải pháp hacky mà tôi có:

import inspect 

def my_decorator(f): 
    args = inspect.getargspec(f).args 
    defined_in_class = bool(args and args[0] == 'self') 
    print "%r: %s" %(f, defined_in_class) 

Nhưng nó chuyển tiếp khi có đối số self trong hàm.

1

Bạn có thể kiểm tra xem trình trang trí có đang được gọi ở cấp mô-đun hay được lồng trong một thứ khác không.

defined_in_class = inspect.currentframe().f_back.f_code.co_name != "<module>" 
+0

Hrm ... Nhưng điều này sẽ không hiệu quả đối với các lớp được lồng trong một cái gì đó, phải không? (ví dụ, 'def mk_class(): class MyClass:…; trả về MyClass') –

+0

Hàm hoặc hàm trả về nào không ảnh hưởng đến giá trị của' inspect.currentframe(). f_back'; điều duy nhất quan trọng là nơi 'my_decorator' được gọi, và đó sẽ luôn là cùng một mức độ mà tại đó thứ nó quấn quanh được xác định. – chepner

+0

Tôi có thể đã hiểu lầm nhận xét của bạn; bất kỳ hàm trang trí nào bên trong các hàm khác sẽ không chạy trình trang trí cho đến khi hàm được bao hàm được gọi, nhưng phạm vi của hàm được bọc sẽ được tính toán chính xác. Bạn có thể sử dụng điều này bên trong trang trí để xem danh sách các phạm vi kèm theo tại thời điểm trang trí được chạy: '[x [0] .f_code.co_name cho x trong inspect.stack() [1:]]'. – chepner

2

Một chút muộn để bên đây, nhưng điều này đã được chứng minh là một phương tiện đáng tin cậy của việc xác định nếu một trang trí đang được sử dụng trên một chức năng được xác định trong một lớp học:

frames = inspect.stack() 

className = None 
for frame in frames[1:]: 
    if frame[3] == "<module>": 
     # At module level, go no further 
     break 
    elif '__module__' in frame[0].f_code.co_names: 
     className = frame[0].f_code.co_name 
     break 

Ưu điểm của phương pháp này trên câu trả lời được chấp nhận là nó hoạt động với ví dụ py2exe.

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