2016-05-08 24 views
3

我在Python 3.5.1上。 我寫了一個庫,並且在某些情況下(當文件直接運行時,即__name__ == '__main__'),我想在某個類中修飾某些方法。它應該修飾所有可以創建的實例。我想以非侵入性的方式來完成,理想情況下,我的圖書館中的類不需要任何特殊的代碼。在Python中修補類的所有實例

一段時間後,我成功地實施這樣的事情,這符合我的要求:

def patch(clazz, name, replacement): 

    def wrap_original(orig): 
     # when called with the original function, a new function will be returned 
     # this new function, the wrapper, replaces the original function in the class 
     # and when called it will call the provided replacement function with the 
     # original function as first argument and the remaining arguments filled in by Python 

     def wrapper(*args, **kwargs): 
      return replacement(orig, *args, **kwargs) 

     return wrapper 

    orig = getattr(clazz, name) 
    setattr(clazz, name, wrap_original(orig)) 

def replacement_function(orig, self, ... other argumnents ...): 
    # orig here is the original function, so can be called like this: 
    # orig(self, ... args ...) 
    pass 

patch(mylib.MyClass, 'method_name', replacemment_function) 

令人驚訝的是,此代碼的工作,雖然我還沒有與類的方法測試過,但我不現在需要它。它還修補修補之前創建的實例,雖然我還不確定它是否良好; d

上面的代碼無疑是困難的,我需要一段時間來圍繞它的工作方式,它,以寫出解釋評論。我會喜歡更容易的事情。

問題:Python庫中是否有任何東西會使這樣的代碼變得不必要,這已經實現了我正在做的事情,但更好?

+0

這看起來類似於我在[這裏](https://github.com/host-anshu/simpleInterceptor/blob/master/interceptor.py)所做的。我也無法想出更簡單的東西。 –

回答

0

之一的海報在這裏,誰黯然刪除她/他的帖子,指引我走向functools模塊。最後,我決定在以下方面:

def replacement(self, orig, ... other arguments ...): 
    # orig here is the original function, so can be called like this: 
    # orig(self, ... args ...) 
    pass 

mylib.MyClass.my_method = functools.partialmethod(replacement, mylib.MyClass.my_method) 

切換的地方所需要的origself參數,爲partialmethod結果結合的第一個參數是在實例,在這種情況下,第二次將是原始函數(partialmethod的第二個參數)。看起來更清潔。

1

你的方法似乎是Pythonic最有效的方法。

Gevent是一種流行的圖書館,它使用猴子修補,performs monkey patching幾乎和你描述的一樣。

3

方法創建時動態在實例上查找時;實例沒有所有方法的副本,而是從該類獲取函數,並根據需要將這些函數綁定到該實例。這就是爲什麼讓班級在這裏工作的原因;當執行屬性查找時,instance.method_name將發現mylib.MyClass.method_name

默認庫中沒有任何內容會執行您在此處執行的操作,不,因爲不同的代碼可能需要不同的模式來處理委派返回到舊方法。

您的方法看起來非常接近how the Mercurial project支持函數包裝,原因是傳遞到包裝。

0

另一種方法是創建一個「空」裝飾功能,然後使用條件邏輯功能和「真正的」裝飾之間切換:

from decorator_lib import real_decorator 

def fake_decorator(fun): 
    return fun 

if __name__ == '__main__': 
    my_decorator = real_decorator 
else: 
    my_decorator = fake_decorator 


# ... elsewhere in the module ... 

@my_decorator 
def method(self, a, b, c): 
    pass 

# ... finally: 
if __name__ == '__main__': 
    run_self_tests_or_whatever() 
+0

這隻有在裝飾發生之前可以進行交換時纔有效。如果類已經存在(並且有實例),如問題所示,在事實之後更改裝飾器不會解決任何問題。 – Blckknght

+0

該OP提到他想要在主要運行時修飾方法。在我看來,這是你可以提前處理的事情。 –

+0

這不需要我更改庫代碼嗎?此外,庫和執行裝飾的文件是分開的,並且在主代碼運行之前定義要裝飾的類。我認爲這不會對我有用,或者我誤解了你的建議。 – wujek