2010-04-27 28 views

回答

14

這一切都取決於情況。例如,如果你使用依賴注入用於測試目的 - 讓您輕鬆模擬出的東西 - 你可以經常放棄注射乾脆:您可以代替模擬出的模塊或類,你否則將注入:

subprocess.Popen = some_mock_Popen 
result = subprocess.call(...) 
assert some_mock_popen.result == result 

subprocess.call()將調用subprocess.Popen(),我們可以嘲笑它,而不必以特殊方式注入依賴項。我們可以直接替換subprocess.Popen。 (這只是一個例子;在現實生活中,你會以更加強大的方式來做到這一點)。

如果在更復雜的情況下使用依賴注入,或者當模擬整個模塊或類不合適時(因爲,例如,你只想嘲笑一個特定的調用),然後使用類屬性或模塊全局變量的依賴是通常的選擇。例如,考慮my_subprocess.py

from subprocess import Popen 

def my_call(...): 
    return Popen(...).communicate() 

您可以輕鬆地只更換Popen呼叫通過my_call()將其分配給my_subprocess.Popen作出;它不會影響任何其他調用subprocess.Popen(但它會取代所有調用my_subprocess.Popen,當然。)同樣,類屬性:

class MyClass(object): 
    Popen = staticmethod(subprocess.Popen) 
    def call(self): 
     return self.Popen(...).communicate(...) 

當使用類屬性就是這樣,這是很少需要考慮的選項,你應該小心使用staticmethod。如果你不這樣做,並且你插入的對象是一個普通的函數對象或另一種類型的描述符,比如一個屬性,當從類或實例中檢索時會做一些特殊的事情,它會做錯誤的事情。更糟的是,如果你現在使用的東西不是描述符(例如類似subprocess.Popen類),它現在可以工作,但如果所討論的對象改變爲正常的未來功能,它將會混淆。

最後,只有簡單的回調;如果你只是想一類的特定實例綁定到特定的服務,你可以通過服務(或一個或多個服務的方法)的類初始化,並將其使用:

class MyClass(object): 
    def __init__(self, authenticate=None, authorize=None): 
     if authenticate is None: 
      authenticate = default_authenticate 
     if authorize is None: 
      authorize = default_authorize 
     self.authenticate = authenticate 
     self.authorize = authorize 
    def request(self, user, password, action): 
     self.authenticate(user, password) 
     self.authorize(user, action) 
     self._do_request(action) 

... 
helper = AuthService(...) 
# Pass bound methods to helper.authenticate and helper.authorize to MyClass. 
inst = MyClass(authenticate=helper.authenticate, authorize=helper.authorize) 
inst.request(...) 

當設置這樣的實例屬性時,你不必擔心描述符被觸發,所以僅僅分配函數(或類或其他可調用的或實例)就沒有問題。

+1

我有C#背景,所以我習慣通過構造函數顯式注入並使用Container解決所有問題。在初始化器中指定缺省值爲None看起來像「窮人的DI」,由於它不夠明確,所以對我來說看起來並不是最佳實踐。關於通過賦值來替換現有方法的類似想法(這就是所謂的「猴子修補」?)。我應該改變我的思維方式,因爲python-way與C#不同嗎? – 2010-04-27 16:25:26

+2

是的,如果你想編寫高效的pythonic代碼,你應該認識到Python是完全不同的語言。你做不同的事情。用於測試的Monkeypatching模塊實際上非常普遍且相當安全(尤其是當使用適當的模擬庫來處理您的細節時)。Monkeypatching類(更改類上的特定方法)是另一回事。 無默認值實際上不是您應該關注的東西 - 該示例實際上通過構造函數非常接近DI,您可以省略默認值,並根據需要將其設置爲必需的參數。 – 2010-04-27 22:16:29

+0

@ThomasWouters你可以解釋一下: _你可以很容易地通過分配my_subprocess.Popen._ 來代替my_call()所做的Popen調用你的意思是'subprocess.call'調用'subprocess.Popen'嗎? – 2013-01-05 00:50:44

1

這個「setter-only」注射配方怎麼樣? http://code.activestate.com/recipes/413268/

這是很Python的,使用「描述」協議與__get__()/__set__(),而是侵入性的,需要用RequiredFeature實例與所需要的Feature的STR-名稱初始化,以取代所有的屬性設定代碼。

0

我最近發佈了一個Python框架,可能會幫助你。我認爲這是一個相當新的嘗試,但我不確定它有多'pythonic'。爲自己判斷。反饋非常歡迎。

https://github.com/suned/serum

0

@Thomas武泰的回答是完全通過。只需添加它:通常我更喜歡最簡單的方法,那些不涉及複雜框架和冗長設置的方法。所以我最常用的是將我的依賴作爲構造函數參數。

問題在於我添加到代碼中的樣板只是爲了保證每個依賴項都被初始化。

作爲裝飾的大風扇從函數範圍中刪除樣板代碼,我只是做了一個裝飾來處理:

@autowired 一個Python 3的裝飾,使簡單,清洗容易依賴注入:

  • 函數根本不必知道自動裝配
  • 依賴關係可以被延遲初始化
  • 調用者能夠顯式地傳遞依賴關係Ÿ情況下,如果需要的話

裝飾的意義就在於這樣的代碼

def __init__(self, *, model: Model = None, service: Service = None): 
    if model is None: 
     model = Model() 

    if service is None: 
     service = Service() 

    self.model = model 
    self.service = service 
    # actual code 

這個

@autowired 
def __init__(self, *, model: Model, service: Service): 
    self.model = model 
    self.service = service 
    # actual code 

沒有複雜的東西,無需安裝,沒有強制實施工作流程。現在你的函數代碼不再與依賴初始化代碼混淆了。

裝飾者的方法是非常簡約的,但它可能是一個完整的框架更適合你的情況。爲此,有很多優秀的模塊,如Injector