为什么IoC/DI在Python中不常见?

429 投票
18 回答
165690 浏览
提问于 2025-04-15 20:31

在Java中,控制反转(IoC)依赖注入(DI)是非常常见的做法,尤其是在网页应用程序、几乎所有的框架和Java EE中都能看到它们的身影。相比之下,虽然Python也有很多大型的网页应用,但除了Zope(听说这个用起来非常糟糕)之外,IoC在Python的世界里似乎并不常见。(如果你觉得我错了,请举一些例子。)

当然,Python中也有一些流行的Java IoC框架的克隆,比如springpython。不过这些框架似乎并没有被广泛使用。至少,我从来没有遇到过基于Djangosqlalchemy加上<insert your favorite wsgi toolkit here>的网页应用使用类似的东西。

我认为IoC有合理的优点,比如可以轻松替换Django的默认用户模型,但在Python中广泛使用接口类和IoC看起来有点奇怪,不太符合“Python风格”。不过,也许有人能更好地解释为什么IoC在Python中没有被广泛使用。

18 个回答

71

这段话主要讲的是Python的模块系统是怎么工作的。你可以通过从模块中导入一个对象,轻松地得到一个“单例”对象。也就是说,你在一个模块里定义了一个对象的实例,然后其他代码只需要导入这个模块,就能直接使用这个已经创建好的对象。

这和Java不一样。在Java中,你不能直接导入对象的实例。这意味着你每次都得自己创建这些对象(或者使用一些依赖注入的方式)。虽然你可以通过静态工厂方法(或者专门的工厂类)来减少自己创建对象的麻烦,但这样每次还是得消耗资源去创建新的对象。

91

在成熟的Python代码中,IoC(控制反转)和DI(依赖注入)是非常常见的。其实你不需要框架就能实现依赖注入,这要归功于鸭子类型。

最好的例子就是你如何使用settings.py来设置一个Django应用:

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': REDIS_URL + '/1',
    },
    'local': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'snowflake',
    }
}

Django Rest Framework在很大程度上使用了依赖注入:

class FooView(APIView):
    # The "injected" dependencies:
    permission_classes = (IsAuthenticated, )
    throttle_classes = (ScopedRateThrottle, )
    parser_classes = (parsers.FormParser, parsers.JSONParser, parsers.MultiPartParser)
    renderer_classes = (renderers.JSONRenderer,)

    def get(self, request, *args, **kwargs):
        pass

    def post(self, request, *args, **kwargs):
        pass

让我提醒一下(来源):

“依赖注入”这个词听起来很高大上,但其实只是一个简单的概念。依赖注入就是给一个对象提供它需要的实例变量。

220

我觉得在Python中,依赖注入(DI)和控制反转(IoC)其实并不算特别罕见。不过,DI/IoC的框架或容器确实比较少见。

想想看,DI容器到底是干嘛的?它的作用是:

  1. 把独立的组件连接成一个完整的应用程序……
  2. ……而且是在运行时进行连接。

我们对“连接”和“运行时”有专门的说法:

  1. 脚本编程
  2. 动态

所以,DI容器其实就是动态脚本语言的一个解释器。让我换个说法:典型的Java或.NET的DI容器,其实就是一个糟糕的解释器,用来处理一种非常糟糕的动态脚本语言,语法丑得令人发指,有时候还基于XML。

在Python中编程时,为什么要使用一种丑陋且糟糕的脚本语言呢?明明有一种既美丽又出色的脚本语言在手!其实,这个问题更普遍:在几乎任何语言中,为什么要用一种丑陋且糟糕的脚本语言,而你手边有Jython和IronPython呢?

总结一下:DI/IoC的“实践”在Python中和在Java中一样重要,原因完全相同。不过,DI/IoC的“实现”在Python中是内置的,通常非常轻量,以至于几乎看不见。

(这里插一句,做个类比:在汇编语言中,调用子程序是个大事——你得把局部变量和寄存器保存到内存中,把返回地址存到某个地方,改变指令指针指向你要调用的子程序,安排好它完成后能跳回你的子程序,把参数放到被调用者能找到的地方,等等。换句话说,在汇编中,“子程序调用”是一种设计模式。在有了像Fortran这样内置子程序调用的语言之前,人们还在自己构建“子程序框架”。你会说在Python中“子程序调用”很少见,仅仅因为你不使用子程序框架吗?)

顺便提一下,如果想看看把DI推向极致是什么样子,可以看看Gilad BrachaNewspeak编程语言以及他在这个主题上的一些文章:

撰写回答