2010-04-27 40 views

Trả lời

14

Tất cả phụ thuộc vào tình huống. Ví dụ, nếu bạn sử dụng dependency injection cho mục đích thử nghiệm - vì vậy bạn có thể dễ dàng thử ra một cái gì đó - bạn thường có thể từ bỏ hoàn toàn tiêm: bạn thay vì có thể thử ra các mô-đun hoặc lớp học mà bạn nếu không sẽ tiêm:

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

subprocess.call() sẽ gọi subprocess.Popen() và chúng tôi có thể giả lập nó mà không cần phải tiêm phụ thuộc theo cách đặc biệt. Chúng tôi chỉ có thể thay thế trực tiếp subprocess.Popen. (Đây chỉ là một ví dụ, trong cuộc sống thực, bạn sẽ làm điều này một cách mạnh mẽ hơn.)

Nếu bạn sử dụng tiêm phụ thuộc cho các tình huống phức tạp hơn hoặc khi mô phỏng toàn bộ mô-đun hoặc lớp học không thích hợp ví dụ, bạn chỉ muốn thử một cuộc gọi cụ thể), sau đó sử dụng các thuộc tính lớp hoặc các phần tử mô-đun cho các phụ thuộc là sự lựa chọn thông thường. Ví dụ, xem xét một my_subprocess.py:

from subprocess import Popen 

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

Bạn có thể dễ dàng thay thế chỉ Popen cuộc gọi được thực hiện bởi my_call() bằng cách gán cho my_subprocess.Popen; nó sẽ không ảnh hưởng đến bất kỳ cuộc gọi khác đến subprocess.Popen (nhưng nó sẽ thay thế tất cả các cuộc gọi đến my_subprocess.Popen, tất nhiên). Tương tự như vậy, lớp thuộc tính:

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

Khi sử dụng thuộc tính lớp như thế này, đó là hiếm khi cần thiết xem xét các tùy chọn , bạn nên cẩn thận để sử dụng staticmethod. Nếu bạn không, và đối tượng bạn đang chèn là một đối tượng hàm bình thường hoặc một kiểu mô tả khác, giống như một thuộc tính, nó thực hiện điều gì đó đặc biệt khi được lấy ra từ một lớp hoặc một cá thể, nó sẽ làm điều sai. Tệ hơn nữa, nếu bạn đã sử dụng cái gì đó ngay bây giờ không phải là một bộ mô tả (như lớp subprocess.Popen, trong ví dụ) nó sẽ hoạt động ngay bây giờ, nhưng nếu đối tượng được đề cập thay đổi thành một hàm bình thường trong tương lai thì nó sẽ gây nhầm lẫn.

Cuối cùng, chỉ có các cuộc gọi lại đơn giản; nếu bạn chỉ muốn kết hợp một trường hợp cụ thể của một lớp với một dịch vụ cụ thể, bạn chỉ có thể chuyển dịch vụ (hoặc một hoặc nhiều phương thức của dịch vụ) tới trình khởi tạo lớp và sử dụng nó:

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(...) 

Khi thiết lập các thuộc tính cá thể như thế, bạn không bao giờ phải lo lắng về việc mô tả kích hoạt, vì vậy chỉ việc gán các hàm (hoặc các lớp hoặc các cuộc gọi hoặc các cá thể khác) là tốt.

+1

Tôi có nền C# vì vậy tôi được sử dụng để tiêm rõ ràng thông qua hàm tạo và sử dụng Vùng chứa để giải quyết mọi thứ. Chỉ định mặc định là None trong initializer trông giống như "man man's DI" không giống như thực hành tốt nhất với tôi vì nó không đủ rõ ràng. Những suy nghĩ tương tự về việc thay thế các phương thức hiện có bằng cách gán (là cái này được gọi là 'vá khỉ '?). Tôi có nên thay đổi suy nghĩ của mình vì cách python khác với C# -way không? –

+2

Có, nếu bạn muốn viết mã pythonic hiệu quả, bạn nên nhận ra rằng Python là một ngôn ngữ hoàn toàn khác. Bạn làm những việc khác nhau một cách khác nhau. Monkeypatching mô-đun để thử nghiệm thực sự là rất phổ biến và khá an toàn (đặc biệt là khi sử dụng một thư viện mocking thích hợp để xử lý các chi tiết cho bạn.) Monkeypatching lớp học (thay đổi phương pháp cụ thể trên các lớp học) là một vấn đề khác nhau. Mặc định None không thực sự là thứ bạn nên tập trung - ví dụ thực sự khá gần DI thông qua hàm tạo, bạn có thể bỏ mặc định và làm cho nó trở thành đối số bắt buộc nếu bạn muốn. –

+0

@ThomasWouters bạn có thể giải thích điều này: _Bạn chỉ có thể dễ dàng thay thế cuộc gọi Popen được thực hiện bởi my_call() bằng cách gán cho my_subprocess.Popen._ Bạn có nghĩa là 'subprocess.call' gọi một cuộc gọi đến' subprocess.Popen'? –

1

Làm thế nào về công thức tiêm "chỉ setter" này? http://code.activestate.com/recipes/413268/

Nó là khá pythonic, sử dụng "mô tả" giao thức với __get__()/__set__(), mà đúng hơn là xâm lấn, đòi hỏi phải thay thế tất cả các mã thuộc tính thiết lập của bạn với một trường hợp RequiredFeature khởi tạo với str-tên của Feature yêu cầu.

0

Gần đây tôi đã phát hành một khung DI cho python có thể giúp bạn ở đây. Tôi nghĩ rằng nó khá mới mẻ, nhưng tôi không chắc nó là 'pythonic' như thế nào. Phán xét cho chính mình. Thông tin phản hồi là rất hoan nghênh.

https://github.com/suned/serum

0

@Thomas Câu trả lời của Wouters hoàn tất và thông qua. Chỉ cần thêm vào nó: thông thường tôi thích các cách tiếp cận đơn giản nhất, những phương thức không liên quan đến các khung công tác phức tạp và các thiết lập chi tiết. Vì vậy, những gì tôi sử dụng nhiều nhất là để có phụ thuộc của tôi như là đối số constructor.

Vấn đề với đó là bản mẫu mà tôi thêm vào mã chỉ để đảm bảo mọi phụ thuộc được khởi tạo.

Là một fan hâm mộ lớn của trang trí để loại bỏ mã boilerplate từ phạm vi chức năng Tôi chỉ cần thực hiện một trang trí để xử lý rằng:

@autowired Một Python 3 trang trí để cho phép dễ dàng và sạch sẽ dependency injection:

  • chức năng không cần phải biết về tự động ở tất cả các phụ kiện
  • phụ thuộc có thể được khởi tạo lười biếng
  • người gọi có thể chuyển rõ ràng phụ thuộc trường y nếu muốn

Toàn bộ vấn đề của trang trí là mã như thế này:

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 

vào này:

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

Không phức tạp, không có thiết lập, không có luồng công việc nào được thực thi. Và bây giờ mã chức năng của bạn không bị lộn xộn với mã khởi tạo phụ thuộc nữa.

Cách tiếp cận trang trí rất tối giản nhưng có thể trường hợp đó là một khung chính thức phù hợp với bạn tốt hơn. Để có được các mô-đun xuất sắc như Injector.

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