2015-10-14 58 views
8

有沒有辦法創建哪個實例可以響應任意方法調用?對所有方法調用做出響應的Python類實例

我知道有一個特殊的方法__getattr__(self, attr)這將當有人試圖訪問實例的屬性被調用。我正在尋找類似的東西,使我能夠攔截方法調用。期望的行爲會是這個樣子:

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


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

編輯

其他建議的問題並沒有解決我的情況:我不想換另一個類或改變第二類的元類或類似。我只想有一個響應任意方法調用的類。

感謝jonrshape我現在知道__getattr__(self, attr)也會在調用方法時被調用,就像訪問屬性時一樣。但是,如果來自方法調用或屬性訪問以及如何獲取潛在方法調用的參數,我如何區分__getattr__

+2

方法是無異於任何其他屬性,你仍然需要'__getattr__' /'__getattribute__'處理。 – jonrsharpe

+0

@jonrsharpe mmh如果我用'__getattr__'實現一個類A並在裏面打印並且執行'a = A(); a.foo'會打印'foo',但如果我打電話'a = A(); a.foo()'它會引發一個'TypeError:'NoneType'對象不可調用' – Salo

+1

'__getattr__'仍然必須*返回*可調用的東西,而不是*調用*它,否則返回默認的'None' – jonrsharpe

回答

7

這是我想出來的東西,它的行爲就好像存在方法一樣。

首先,我們建立了一兩件事:你不能在__getattr__區分,如果attr來自函數調用或「屬性訪問」,因爲類方法是你的類的屬性。因此,有人可以訪問方法,即使他們不打算叫它,如:

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 

因此,我能想到的最接近的是這種行爲:

創建A級,這樣的即:

  1. 當我們嘗試訪問已存在於該類中的屬性/方法時,將正常運行並返回請求的屬性/方法。
  2. 當我們嘗試訪問類定義中不存在的東西時,將其視爲一個類方法,並對所有這些方法都有1個全局處理程序。

我將首先編寫類定義,然後說明如何訪問不存在的方法的行爲與訪問存在的方法完全相同,無論您是隻是訪問它,還是實際調用它。

類定義:

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) 

我添加的方法real_method只是讓我有一個真正的類存在與一個「虛方法」

這裏來比較其行爲的東西,結果如下:

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

感謝您的回答,讓我更深入地理解Python。我發現的唯一的東西:如果我打電話給'a.attribute_that_doesnt_exist',它將沒有任何響應 – Salo

+1

@Salo如果你調用'a.attribute_that_doesnt_exist'它不應該沒有任何響應('None')。它實際上應該返回一個「綁定方法」對象。 所以:'a.method'返回方法。如果在方法調用('a.method()')之後添加括號'()'(帶參數,可選),它將被評估。 打開一個python解釋器('python',理想的是'ipython'),並鍵入'a.attribute_that_doesnt_exist'。你應該像'>' 這是python告訴你這是一個類方法,但你沒有調用它的方式。 – tomas

+0

你是對的,再次感謝 – Salo

0

方法調用與屬性訪問沒有任何區別。 __getattr__()__getattribute__()是響應任意屬性請求的方式。

您無法知道訪問是來自「正在檢索」還是「方法調用」。

它的工作原理是這樣的:第一,屬性檢索,然後,調用檢索對象(在Python,通話只是另算:什麼都可以調用,將拋出一個異常,如果它是不可調用的)。一個人不會,也不應該知道另一個人(也就是說,你可以在調用堆棧中分析代碼,但這完全不是這裏要做的)。

其中一個原因是 - 函數是Python中的第一類對象,即一個函數(或者說,對它的引用)與任何其他數據類型沒有區別:我可以獲取引用,將其保存並通過它。即請求數據字段和方法之間完全沒有區別。

詳細說明您需要什麼,以便我們提出更好的解決方案。例如,如果您需要能夠使用不同簽名調用「方法」,則需要*args**kwargs

4

unittest.mock.Mock默認會這樣做。

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 
相關問題