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

tux*_*21b 296 python architecture design-patterns dependency-injection inversion-of-control

在Java中,IoC/DI是一种非常常见的做法,广泛用于Web应用程序,几乎所有可用的框架和Java EE.另一方面,也有很多大的Python Web应用程序,但除了Zope(我听说代码应该非常糟糕),IoC在Python世界中似乎并不常见.(如果你认为我错了,请说出一些例子).

当然有几个流行的Java IoC框架克隆可用于Python,例如springpython.但它们似乎都没有被实际使用.至少,我从来没有在一个stumpled Django的SQLAlchemy的 + <insert your favorite wsgi toolkit here>,它使用类似的东西,基于Web应用程序.

在我看来,IoC具有合理的优势,并且可以很容易地替换django-default-user-model,但是在Python中广泛使用接口类和IoC看起来有点奇怪而不是"pythonic".但也许有人有更好的解释,为什么IoC没有在Python中广泛使用.

Jör*_*tag 189

我实际上并不认为DI/IoC 在Python 不常见.什么不常见的,但是,是DI/IoC的框架/容器.

想想看:DI容器有什么作用?它允许你

  1. 将独立组件连接成一个完整的应用程序......
  2. ......在运行时.

我们有"连接在一起"和"在运行时"的名称:

  1. 脚本
  2. 动态

因此,DI容器只不过是动态脚本语言的解释器.实际上,让我重新说一下:一个典型的Java/.NET DI容器只不过是一个糟糕的解释器,用于一个非常糟糕的动态脚本语言,它有一种非常丑陋的,有时候是基于XML的语法.

当你用Python编程时,为什么你会想要使用一种丑陋的,糟糕的脚本语言,当你拥有一个漂亮,优秀的脚本语言时?实际上,这是一个更普遍的问题:当您使用几乎任何语言进行编程时,为什么当您使用Jython和IronPython时,您是否想要使用一种丑陋,糟糕的脚本语言?

因此,回顾一下:出于完全相同的原因,DI/IoC 的实践在Python中与在Java中一样重要.然而,DI/IoC 的实现内置于语言中,并且通常非常轻巧,完全消失.

(这里有一个简单的比喻:在汇编中,子程序调用是一个非常重要的交易 - 你必须将你的局部变量和寄存器保存到内存,保存你的返回地址,将指令指针更改为你调用的子程序,安排它以某种方式跳回你的子程序完成后,将参数放在被调用者可以找到它们的地方,依此类推.IOW:在汇编中,"子程序调用"是一个设计模式,之前有类似的语言Fortran内置了子程序调用,人们正在构建自己的"子程序框架".你会说Python中的子程序调用"不常见",只是因为你没有使用子程序框架吗?)

顺便说一句:有关将DI 纳入其逻辑结论的示例,请查看Gilad Bracha的新闻编程语言及其关于该主题的着作:

  • downvote,连接在一起与脚本无关,DI是一种模式,它不等同于脚本 (132认同)
  • "然而,DI/IoC的实现是内置于语言中的,并且通常非常轻巧,以至于它完全消失了." 投票不力,因为这绝对是不真实的.DI是一种将接口传递给构造函数的模式.它不是内置于python中. (119认同)
  • 虽然我同意.XML注释是错误的.许多(至少是现代的)IOC容器使用约定(代码)而不是配置(XML). (53认同)
  • 我不同意这一点.DI并没有解决静态语言中缺乏动态脚本的问题.它提供了一个用于配置和组合应用程序部件的框架.我曾经听过一个Ruby开发人员说在动态语言中DI是不必要的.但是他使用了Rails ...... Rails只是一个很大的DI容器,它使用约定来确定何时配置哪些部分.他不需要DI,因为Rails解决了为他找到零件的问题. (33认同)
  • 没有什么可以阻止您在Java中明确地编写接线,但随着您拥有越来越多的服务,依赖性变得更加复杂.DI容器类似于Make:您声明依赖项,容器以正确的顺序初始化它们.Guice是一个Java DI框架,其中所有内容都是用Java代码编写的.通过声明性地编写DI容器还增加了对初始化之前的处理后处理的支持(例如,用实际值替换属性占位符) (17认同)
  • @GillBates我投了这个投票,因为注入依赖关系*不是*在任何情况下*'内置于语言中'.Python几乎没有接口的概念,没有abc或zope.interface; 建议它有一个*内置*解决方案,用于将带有递归依赖*的*接口实现注入依赖类中,这是错误的.答案中的情绪可能是有效的,但是当它说出错误的事情时,这并不能使答案正确. (14认同)
  • Downvoted.关于语言的精彩程度太过喋喋不休,但对上述模式的反应从根本上说是错误的. (12认同)
  • downvote,"连接在一起"和"在运行时"是有点正确但"脚本"和"动态"是错误的类比! (8认同)
  • 多么傲慢的答案.如果实例化对象,则无论语言如何,该对象都可能具有依赖关系.有人必须提供这些依赖项.DI框架使用容器集中管理."DI是翻译"什么? (7认同)
  • 显然基于对DI是什么的基本误解,以及DI框架通常做什么.很抱歉看到这样一个有偏见,无内容的答案被认为是正确的. (6认同)
  • 我敢说这是我在StackExchange上看到的最好的答案,错误或正确并不重要,但这里的答案+评论的组合给出了对DI的最好理解甚至比直接问题更好:什么是DI?这是不幸的,虽然我无法进行投票,因为这是一个答案可靠性的指标(我猜!). (5认同)
  • Downvote,这个答案完全是误导性的,应该是不被接受的.依赖注入主要是为了允许控制的反转 - 我不想在调用者类中知道,我需要哪个被调用者的实现.就像拥有连接到数据库的API一样.我不想在每个实例中实例化PostgresDBWriter类,我想使用DBWrite来允许将实现更改为Mysql.动态与DI无关 - 即使js也是动态的,javascript框架也使用DI. (5认同)
  • @GillBates和其他人.依赖注入意味着为对象提供实例变量.您可以将接口静态绑定到对象的实例化方法中,这样可以更好地查找和"对接口编程"的强大支持.答案是对的.动态语言和鸭子类型只是在运行时注入依赖项,因为对象对调用者一无所知.然而,一些python开发人员练习在文档字符串中记录"接口",因此您也可以使用IDE支持对接口进行编程. (4认同)
  • downvote,自以为是的咆哮,并没有真正回答这个问题.当然你可以编写自己的IOC容器,这并不意味着你总是想要这样做 (4认同)
  • 投票,但不完全是出于上述原因.答案无法证明它的前提是合理的,或者是如此糟糕,以至于我和其他许多人无法分辨.特别是声称DI是脚本解释器并且DI内置于该语言中需要比提供的更多支持.Downvote将在编辑时删除,更好的支持和代码示例显示python如何满足其他语言中现代DI容器的要求. (4认同)
  • 考虑到回答者已经完全错误了DI,他们对赞成票的数量感到惊讶.我伤心. (4认同)
  • Downvoted,不正确.当物体被构造和破坏以及额外的清理时,DI还可以控制寿命.Javascript作为动态语言也有大量的DI框架.Angular没有适当的DI(更多是AMD加载器),他们必须在Angular 2.0中修复它. (3认同)
  • 仍在等待python注入我的依赖项。 (3认同)
  • 传统的方式是将A对象与A对象自身的B对象关联起来,而DI的关键思想是将这种连线分离在其他地方。无论您谈论的是脚本,XML还是运行时,都不是重点。 (2认同)
  • 不赞成投票。DI不仅是运行时检查的体现,而且还是一项设计决策。没有什么可以阻止python程序员实例化对象** _ inside _ **方法,从而将它们耦合到该方法,从而使它们不可注入。我不相信DI是内置在python中的,尽管我同意鸭子的输入有助于清除Java中许多常见的DI残体。 (2认同)
  • 代码示例好吗? (2认同)
  • 在不提供任何内幕或参考的情况下,贬低某事物是美丽的还是丑陋的。答案更像是右翼政治而非技术。 (2认同)

TM.*_*TM. 46

部分原因是模块系统在Python中的工作方式.你可以免费获得一种"单身",只需从模块中导入即可.在模块中定义对象的实际实例,然后任何客户端代码都可以导入它并实际获得一个工作的,完全构造/填充的对象.

这与Java不同,在Java中您不导入实际的对象实例.这意味着你总是必须自己实例化它们(或者使用某种IoC/DI风格的方法).您可以通过使用静态工厂方法(或实际工厂类)来减轻必须自己实例化所有内容的麻烦,但是每次实际创建新方法时仍然会产生资源开销.

  • 过度简化,回答,在现实生活中,你很少需要"单身",你需要控制范围(你可能需要一个线程本地单例,或者一个会话单例,等等),这让我觉得那种问题在Python中解决的并不是企业环境中实际解决的现实问题 (12认同)
  • 实际上,DI 是关于能够测试和解耦代码的依赖关系。此外,导入功能类似于 Java 中的静态导入,它让我导入对象的单个实例。 (4认同)
  • 那讲得通.如果我想在Python中更改实现,我只需使用相同的名称从不同的位置导入.但现在我想通过在Java中为每个`MyClass`定义一个`MyClassInstances`类来反过来也是可能的,它只包含静态的,完全初始化的实例.这将是有线的:D (2认同)
  • 另一个想法是:提供一种在python中改变这种导入的方法,它可以在不触及所有python文件的情况下轻松替换实现.而不是`来自framework.auth.user导入用户',最好在内部编写`User = lookup('UserImplentation','framework.auth.user.User')`(第二个参数可能是默认值)框架.然后框架的用户将能够替换/专门化`User`实现而无需触及框架. (2认同)
  • “您可以免费获得某种“单例”,只需从模块中导入它即可。”可以在 Java 中通过声明一个静态实例字段并将其设置为一个值来轻松完成。这不是溶胶 (2认同)

Max*_*ysh 35

IoC和DI在成熟的Python代码中非常常见.由于鸭子打字,你只需要一个框架来实现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',
    }
}
Run Code Online (Sandbox Code Playgroud)

Django Rest Framework大量使用DI:

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
Run Code Online (Sandbox Code Playgroud)

让我提醒一下(来源):

对于5美分的概念,"依赖注入"是一个25美元的术语.[...]依赖注入意味着为对象提供其实例变量.[...].

  • 错误...这不是依赖注入,因为实例(`IsAuthenticated`、`ScopedRateThrottle`)是由类实例化的。它们不会传递到构造函数中。 (11认同)
  • +1。说得好。作为一名Python程序员,我完全被C#中的DI框架的整个采访演示所困扰。花了我一段时间才意识到我已经在Flask应用程序中一直做到这一点,甚至没有考虑过,因为您不需要框架。对于一个除了C#/ Java之外一无所知的人,这个问题很有意义。对于鸭子式语言程序员来说,这很自然,就像您所说的,“ 5美分的概念用25美元的术语”。 (6认同)
  • @alex 不,您不需要更改代码来使用另一个渲染器。您甚至可以同时使用多个渲染器:“renderer_classes = (JSONRenderer, BrowsableAPIRenderer, XMLRenderer)”。模拟就像`@unittest.patch('myapp.views.FooView.permission_classes')`一样简单。迫切需要“传递某些东西”是“Java 做事方式”的结果,因为 Java 是一种编译型静态类型语言,缺乏强大的元编程功能。 (6认同)
  • @MaxMalysh 我同意 dopatraman 在这一点上的观点。这甚至不是 IoC,因为类本身具有对特定类的“硬编码”依赖关系。在 IoC 中,应该提供依赖关系而不是硬编码。最重要的是,在依赖注入中,您将有一个实体负责管理每个服务的生命周期,并在这种情况下注入它们。所提供的解决方案不是其中任何一个。 (4认同)
  • @MaxMalysh 我也不同意。如果我们坚持常见的 DI 定义:“依赖注入是一种技术,其中一个对象(或静态方法)提供另一个对象的依赖关系”,那么使用 `settings.py` 没问题,但对于 `FooView` 视图无效,因为视图它声明它自己的属性(permission_classes 等)并且不从外部接收它们。 (4认同)
  • 首先,您正在混合 IoC 和 DI。它们不是同一件事。IoC 是一个通用概念,与特定语言的语义无关。你可以在 Lisp 或普通 C 中使用 IoC。至于 DI,你实际上谈论的是 DI **框架**如何在 **Java 和 C#** 中工作。即使在 Java / C# 中,也有多种方法可以实现简单的 DI(例如,使用构造函数、使用 setter 方法、使用接口)。至于 `FooView`...这些依赖项_不是硬编码的_;您可以用一行代码交换或模拟它们。鸭子。打字。 (3认同)
  • @MaxMalysh,无论您是否传递模拟,您都不应该在类中触及 1 行代码...... (3认同)
  • 这不是DI。如果您想从 JSONRenderer 切换到 XMLRenderer 怎么办?您现在必须更改您的代码。我真的很好奇你如何在单元测试中模拟 IsAuthenticated、ScopedRateThrottle 或其他类,因为你没有地方可以传递它。 (3认同)
  • IsAuthenticated和ScopedRateThrottle不是实例,它们是类。当构造FooView时(实际上,当FooView处理请求时)将实例化它们。无论如何,这仅仅是实现细节。IsAuthenticated和ScopedRateThrottle是依赖项;它们被注入`FooView`。_when_或_how_完成都无关紧要。Python不是Java,因此有不同的实现方法。 (2认同)
  • @PmanAce 是的,如果您传递模拟,代码不会改变。您可以交换_依赖关系_或在_内部测试_中模拟它们。原始代码保持不变。 (2认同)

Dan*_*wby 34

Django充分利用了控制反转.例如,配置文件选择数据库服务器,然后框架为数据库客户端提供适当的数据库包装器实例.

不同之处在于Python具有一流的类型.数据类型(包括类)本身就是对象.如果您想要某些东西使用特定的类,只需为该类命名即可.例如:

if config_dbms_name == 'postgresql':
    import psycopg
    self.database_interface = psycopg
elif config_dbms_name == 'mysql':
    ...
Run Code Online (Sandbox Code Playgroud)

稍后代码可以通过编写以下内容来创建数据库接口

my_db_connection = self.database_interface()
# Do stuff with database.
Run Code Online (Sandbox Code Playgroud)

而不是Java和C++需要的样板工厂函数,Python使用一行或两行普通代码.这是功能与命令式编程的强项.

  • 以上代码*如何被认为是必要的? (87认同)
  • 呃.你在那里做的几乎是教科书当务之急丹尼尔. (25认同)
  • 你所谓的代码实际上就是接线部分.这将是您的ioc框架的XML.它实际上可以简单地写成`import psycopg2 as database_interface`.把那行放在`injections.py`和voilà中. (4认同)
  • 这不仅仅是头等功能吗?https://en.wikipedia.org/wiki/First-class_function仅仅因为你拥有并使用它们不会使你的代码具有功能性.这里发生了很多副作用(例如更改`self.database_interface`),这些副作用是尖锐的. (4认同)

bca*_*lso 12

几年没有使用过Python,但我会说它与动态类型语言有关,而不是其他任何东西.举一个简单的例子,在Java中,如果我想测试那些写入标准的东西,我可以使用DI并传入任何PrintStream来捕获正在编写的文本并进行验证.然而,当我在Ruby中工作时,我可以动态地替换STDOUT上的'puts'方法来进行验证,从而使DI完全脱离图片.如果我创建抽象的唯一原因是测试使用它的类(想想文件系统操作或Java中的时钟),那么DI/IoC会在解决方案中产生不必要的复杂性.

  • 人们愿意改变系统的工作方式来测试它是否有效,这让我感到惊讶.现在您需要测试您的测试不会产生副作用. (3认同)
  • 他谈到只在测试范围内改变 puts 方法,就像注入对象的模拟方法。 (3认同)
  • 对我来说,可测试性是头等大事。如果一个设计不可测试,那么它就不是一个好的设计,并且我可以毫无问题地更改设计以使其更具可测试性。我必须重新验证它是否仍然有效,但没关系。在我看来,可测试性是更改代码的一个完全有效的理由 (3认同)
  • @基本在*单元测试*中很正常,实际上建议在这些测试中执行此操作,因为您不想用一个以上的代码块(正在测试的代码)来污染测试用例的覆盖范围。但是对于集成测试这样做是错误的,也许这就是您在评论中指的是什么? (2认同)

小智 9

IoC/DI是一个设计概念,但不幸的是,它通常被视为适用于某些语言(或打字系统)的概念.我很想看到依赖注入容器在Python中变得更加流行.有Spring,但这是一个超级框架,似乎是Java概念的直接端口,没有太多考虑"Python方式".

鉴于Python 3中的Annotations,我决定对一个功能齐全但简单的依赖注入容器进行破解:https://github.com/zsims/dic.它基于.NET依赖注入容器中的一些概念(如果您曾在该空间中玩过,IMO非常棒),但是使用Python概念进行了变异.


jho*_*ira 9

它认为人们真的不会得到依赖注入和控制反转意味着什么.

使用控制反转的实践是拥有依赖于另一个类或函数的类或函数,但不是在函数代码类中创建实例,而是最好将其作为参数接收,因此可以实现松散耦合.这具有许多好处,因为它具有更高的可测试性并且可以实现liskov替换原则.

您可以看到,通过使用接口和注入,您的代码变得更加可维护,因为您可以轻松地更改行为,因为您不必重写一行代码(可能是DI配置中的一行或两行) class来改变它的行为,因为实现你的类等待的接口的类可以独立地变化,只要它们遵循接口.保持代码解耦和易于维护的最佳策略之一是至少遵循单一的责任,替代和依赖倒置原则.

什么是DI库,如果你可以自己在一个包中自己实例化一个对象并导入它自己注入它是什么?选择的答案是正确的,因为java没有程序部分(类之外的代码),所有这些都进入无聊的配置xml,因此需要一个类来实例化并注入依赖于延迟加载的方式,这样你就不会流失你的性能,在python上,你只需在代码的"程序"(代码外)代码段中编写注入代码

  • @LieRyan,您可以通过反射来做到这一点,或者,如果您经常或在运行时需要它,您可以从另一种语言调用静态语言,例如 Groovy(旨在轻松使用 Java),甚至是 Python 本身。然而,这与 IoC/DI 框架关系不大,因为它们的目的是仅利用定义自动为您完成大部分过程对象连接。可悲的是,大多数特此回答都忽略了这一点。 (3认同)
  • 您仍然怀念 IoC/DI 自动将对象连接在一起的情况。在运行时能够做到这一点并不多(Java 无论如何都可以通过反射来做到这一点),而是框架会处理它,并且您不需要显式地执行它。拥有过程部分也是无关紧要的,没有什么可以阻止人们用 Java 编写一个完全过程化的应用程序,通过使用类作为静态子例程和函数的容器,而不使用 OOP 功能。 (2认同)
  • @zakmck:Python 的“过程”部分实际上并不是关于编写过程代码。Python 的“过程”部分与静态语言的不同之处在于能够将过程代码放入类定义期间运行的类主体中,并将​​ import 语句放入 if 语句中,并通过定义类来创建类工厂在工厂方法内部。这些是静态语言无法真正做到的事情,但它解决了 IOC/DI 试图解决的大部分问题。Python 中的元编程通常看起来就像常规的 Python 代码。 (2认同)

mlv*_*ljr 7

实际上,使用DI编写足够干净和紧凑的代码是非常容易的(我想,它会保持pythonic然后,但无论如何:)),例如我实际上通过这种编码方式:

def polite(name_str):
    return "dear " + name_str

def rude(name_str):
    return name_str + ", you, moron"

def greet(name_str, call=polite):
    print "Hello, " + call(name_str) + "!"
Run Code Online (Sandbox Code Playgroud)

_

>>greet("Peter")
Hello, dear Peter!
>>greet("Jack", rude)
Hello, Jack, you, moron!
Run Code Online (Sandbox Code Playgroud)

是的,这可以被视为参数化函数/类的一种简单形式,但它可以完成它的工作.所以,也许Python的默认包含电池也足够了.

PS我还在动态评估Python中的简单布尔逻辑时发布了一个更大的这种天真方法的例子.

  • 对于可能有效的简单案例,只需想象一个简单的Web博客控制器,它使用各种模型(Post,Comment,User).如果您希望用户注入自己的Post模型(带有额外的viewcount属性来跟踪它),以及他自己的User模型以及更多的配置文件信息等等,所有参数可能看起来很混乱.此外,用户可能也想要更改Request对象,以支持文件系统会话,而不是简单的基于cookie的会话或类似的东西......所以,您很快就会得到很多参数. (3认同)

小智 7

我认为由于 python 的动态特性,人们通常不会看到需要另一个动态框架。当一个类从新样式的“对象”继承时,您可以动态创建一个新变量(https://wiki.python.org/moin/NewClassVsClassicClass)。

在普通python中:

#application.py
class Application(object):
    def __init__(self):
        pass

#main.py
Application.postgres_connection = PostgresConnection()

#other.py
postgres_connection = Application.postgres_connection
db_data = postgres_connection.fetchone()
Run Code Online (Sandbox Code Playgroud)

但是,查看https://github.com/noodleflake/pyioc,这可能就是您要查找的内容。

在 pyioc

from libs.service_locator import ServiceLocator

#main.py
ServiceLocator.register(PostgresConnection)

#other.py
postgres_connection = ServiceLocator.resolve(PostgresConnection)
db_data = postgres_connection.fetchone()
Run Code Online (Sandbox Code Playgroud)

  • 两个版本使用相同数量的代码这一事实在很大程度上解释了为什么使用框架不是很流行。 (2认同)

osp*_*der 7

查看 FastAPI,它内置了依赖注入。例如:

from fastapi import Depends, FastAPI

async def get_db():
    db = DBSession()
    try:
        yield db
    except Exception:
        db.rollback()
        raise
    finally:
        db.close()

app = FastAPI()

@app.get("/items")
def get_items(db=Depends(get_db)):
    return db.get_items()
Run Code Online (Sandbox Code Playgroud)


小智 5

我支持"JörgWMittag"回答:"DI/IoC的Python实现非常轻巧,完全消失了".

要备份此声明,请查看着名的Martin Fowler从Java移植到Python的示例:Python:Design_Patterns:Inversion_of_Control

从上面的链接可以看出,Python中的"Container"可以用8行代码编写:

class Container:
    def __init__(self, system_data):
        for component_name, component_class, component_args in system_data:
            if type(component_class) == types.ClassType:
                args = [self.__dict__[arg] for arg in component_args]
                self.__dict__[component_name] = component_class(*args)
            else:
                self.__dict__[component_name] = component_class
Run Code Online (Sandbox Code Playgroud)

  • 即使是最弱的DI容器也远远不够.生命周期管理,递归依赖性解析,模拟能力,或者 - 失败所有 - 配置在哪里?这只不过是一个类型查找和缓存,它与IoC不同. (38认同)
  • 几年前,我使用元类作为练习编写了一个 [小型 DI 框架](https://github.com/soulrebel/diy/blob/master/diy.py)。整个过程是一个具有零导入和 doctests 的单个文件,这使得它不言自明。它表明,以一种甚至是“pythonic”的方式实现基本功能并不难,但我真诚地认为,没有一个完整的解决方案像 Java 中的 Spring 那样受到广泛关注,而且每个人都在做自定义插件架构,这真是令人遗憾。 (2认同)

小智 5

pytest 固定装置均基于 DI(