这一切都取决于情况.例如,如果您使用依赖注入来进行测试,所以您可以轻松地嘲笑某些内容 – 您可以经常放弃注入:您可以嘲笑您将注入的模块或类:
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()
您只能通过分配给my_subprocess.Popen来轻松替换由my_call()创建的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(...)
当设置这样的实例属性时,您不必担心描述符触发,所以只需分配函数(或类或其他可调用或实例)即可.