2015-10-14 15 views
8

Có cách nào để tạo một lớp mà các cá thể phản ứng với các cuộc gọi phương thức tùy ý không?Ví dụ của lớp Python trả lời tất cả các cuộc gọi phương thức

Tôi biết có một phương pháp đặc biệt __getattr__(self, attr) sẽ được gọi khi ai đó đang cố truy cập thuộc tính của một cá thể. Tôi đang tìm kiếm một cái gì đó tương tự cho phép tôi để đánh chặn các cuộc gọi phương pháp, quá. Các hành vi mong muốn sẽ giống như thế này:

class A(object): 
    def __methodintercept__(self, method, *args, **kwargs): # is there a special method like this?? 
     print(str(method)) 


>>> a = A() 
>>> a.foomatic() 
foomatic 

EDIT

các câu hỏi gợi ý khác không giải quyết trường hợp của tôi: Tôi không muốn quấn một lớp khác hay thay đổi metaclass một lớp thứ hai hoặc giống. Tôi chỉ muốn có một lớp học đáp ứng các cuộc gọi phương thức tùy ý.

Nhờ jonrshape bây giờ tôi biết rằng __getattr__(self, attr) cũng sẽ được gọi khi phương thức được gọi theo cách giống như khi một thuộc tính được truy cập. Nhưng làm thế nào để tôi phân biệt trong __getattr__ nếu attr xuất phát từ một cuộc gọi phương thức hoặc truy cập thuộc tính và cách nhận thông số của một cuộc gọi phương thức tiềm năng?

+2

Phương thức được xử lý không khác với bất kỳ thuộc tính nào khác, bạn vẫn cần '__getattr__' /' __getattribute__'. – jonrsharpe

+0

@jonrsharpe mmh nếu tôi thực hiện một lớp A với '__getattr__' và một bản in bên trong và làm' a = A(); a.foo' nó sẽ in 'foo' nhưng nếu tôi gọi' a = A(); a.foo() 'nó sẽ tăng một đối tượng' TypeError: 'NoneType' không được gọi là ' – Salo

+1

'__getattr__' sẽ vẫn phải * return * một cái gì đó có thể gọi được, không * gọi * nó, nếu không bạn sẽ nhận được' None' mặc định – jonrsharpe

Trả lời

7

Đây là thứ tôi đã nghĩ ra, sẽ hoạt động chính xác như phương pháp tồn tại.

Đầu tiên chúng ta hãy thiết lập một điều: Bạn không thể phân biệt trong __getattr__ nếu attr xuất phát từ một cuộc gọi hàm hoặc một "truy cập thuộc tính", bởi vì một phương pháp học là một thuộc tính của lớp học của bạn.Vì vậy, ai đó có thể truy cập vào phương pháp mà ngay cả khi họ không có ý định gọi nó, như trong:

class Test: 
    def method(self): 
     print "Hi, I am method" 

>> t = Test() 
>> t.method # just access the method "as an attribute" 
<bound method Test.method of <__main__.Test instance at 0x10a970c68>> 

>> t.method() # actually call the method 
Hi, I am method 

Chính vì vậy, điều gần gũi nhất tôi có thể nghĩ đến là hành vi này:

Tạo một lớp Một, chẳng hạn rằng:

  1. Khi chúng tôi cố gắng truy cập thuộc tính/phương pháp đã tồn tại trong lớp đó, hoạt động bình thường và chỉ trả về thuộc tính/phương thức được yêu cầu.
  2. Khi chúng tôi cố gắng truy cập nội dung không tồn tại trong định nghĩa lớp, hãy coi đó là phương thức lớp và có 1 trình xử lý chung cho tất cả các phương pháp như vậy.

Trước tiên tôi sẽ viết định nghĩa lớp và sau đó cho biết cách truy cập một phương thức không tồn tại giống như truy cập vào phương thức tồn tại, cho dù bạn đang truy cập hoặc thực sự gọi nó.

định nghĩa Class:

class A(object): 
    def __init__(self): 
     self.x = 1 # set some attribute 

    def __getattr__(self,attr): 
     try: 
      return super(A, self).__getattr__(attr) 
     except AttributeError: 
      return self.__get_global_handler(attr) 

    def __get_global_handler(self, name): 
     # Do anything that you need to do before simulating the method call 
     handler = self.__global_handler 
     handler.im_func.func_name = name # Change the method's name 
     return handler 

    def __global_handler(self, *args, **kwargs): 
     # Do something with these arguments 
     print "I am an imaginary method with name %s" % self.__global_handler.im_func.func_name 
     print "My arguments are: " + str(args) 
     print "My keyword arguments are: " + str(kwargs) 

    def real_method(self, *args, **kwargs): 
     print "I am a method that you actually defined" 
     print "My name is %s" % self.real_method.im_func.func_name 
     print "My arguments are: " + str(args) 
     print "My keyword arguments are: " + str(kwargs) 

tôi đã thêm các phương pháp real_method chỉ vì vậy tôi có cái gì đó thực sự tồn tại trong lớp để so sánh hành vi của nó với điều đó của một 'phương pháp tưởng tượng'

Dưới đây là kết quả:

>> a = A() 
>> # First let's try simple access (no method call) 
>> a.real_method # The method that is actually defined in the class 
<bound method A.real_method of <test.A object at 0x10a9784d0>> 

>> a.imaginary_method # Some method that is not defined 
<bound method A.imaginary_method of <test.A object at 0x10a9784d0>> 

>> # Now let's try to call each of these methods 
>> a.real_method(1, 2, x=3, y=4) 
I am a method that you actually defined 
My name is real_method 
My arguments are: (1, 2) 
My keyword arguments are: {'y': 4, 'x': 3} 

>> a.imaginary_method(1, 2, x=3, y=4) 
I am an imaginary method with name imaginary_method 
My arguments are: (1, 2) 
My keyword arguments are: {'y': 4, 'x': 3} 

>> # Now let's try to access the x attribute, just to make sure that 'regular' attribute access works fine as well 
>> a.x 
1 
+0

Cảm ơn câu trả lời của bạn, đã giúp tôi hiểu sâu hơn về Python. Điều duy nhất tôi tìm thấy: nếu tôi gọi 'a.attribute_that_doesnt_exist' nó sẽ trả lời với không có gì – Salo

+1

@ Salo Nếu bạn gọi' a.attribute_that_doesnt_exist', nó sẽ không trả lời với không có gì ('None'). Nó thực sự sẽ trả về một đối tượng "phương thức ràng buộc". Vì vậy: 'a.method' trả về phương thức. Nếu bạn thêm các dấu ngoặc đơn '()' (với các đối số, tùy chọn) sau khi gọi phương thức ('a.method()'), nó sẽ được đánh giá. Mở trình thông dịch python ('python', lý tưởng' ipython') và nhập 'a.attribute_that_doesnt_exist'. Bạn sẽ nhận được sth như '>' Đây là cách của python cho bạn biết rằng đây là một phương thức lớp, nhưng bạn không gọi nó. – tomas

+0

Bạn nói đúng, cảm ơn một lần nữa – Salo

0

Cuộc gọi phương thức không khác biệt với quyền truy cập thuộc tính. __getattr__() hoặc __getattribute__() là cách phản hồi các yêu cầu thuộc tính tùy ý.

Bạn không thể biết liệu truy cập có đến từ "chỉ truy xuất" hoặc "gọi phương thức" hay không.

Nó hoạt động như thế này: đầu tiên, truy xuất thuộc tính, sau đó, gọi trên đối tượng đã truy xuất (bằng Python, cuộc gọi chỉ là toán tử khác: bất cứ điều gì có thể được gọi và sẽ ném ngoại lệ nếu không gọi được). Một không, và không nên, biết về người khác (tốt, bạn có thể phân tích mã lên ngăn xếp cuộc gọi, nhưng đó hoàn toàn không phải là điều cần làm ở đây).

Một trong những lý do là - hàm là đối tượng hạng nhất trong Python, nghĩa là hàm (hoặc, tham chiếu đến nó) không khác với bất kỳ loại dữ liệu nào khác: Tôi có thể lấy tham chiếu, lưu và vượt qua. I E. hoàn toàn không có sự khác biệt giữa yêu cầu một trường dữ liệu và một phương thức.

Xây dựng dựa trên những gì bạn cần để chúng tôi đề xuất giải pháp tốt hơn.

Ví dụ: nếu bạn cần "phương pháp" để có thể được gọi với các chữ ký khác nhau, *args**kwargs là cách để thực hiện.

4

unittest.mock.Mock thực hiện việc này theo mặc định.

from unittest.mock import Mock 

a = Mock() 

a.arbitrary_method()        # No error 
a.arbitrary_method.called      # True 
a.new_method 
a.new_method.called        # False 
a.new_method("some", "args") 
a.new_method.called        # True 
a.new_method.assert_called_with("some", "args") # No error 
a.new_method_assert_called_with("other", "args") # AssertionError 
Các vấn đề liên quan