为什么 IoC/DI 在 Python 中不常见?
在 Java 中 IoC / DI 是一种非常常见的做法,广泛用于 Web 应用程序、几乎所有可用的框架和 Java EE。另一方面,也有很多大型 Python Web 应用程序,但除了 Zope(我听说它编码起来真的很糟糕)之外,IoC 在 Python 世界中似乎并不常见。 (如果您认为我错了,请举一些例子)。
当然,有几个流行的 Java IoC 框架可用于 Python,例如 springpython。但它们似乎都没有被实际使用。至少,我从来没有遇到过 Django 或 sqlalchemy+<在此处插入您最喜欢的 wsgi 工具包>
基于 Web 应用程序,它使用类似的东西。
在我看来,IoC 具有合理的优势,并且可以轻松替换 django-default-user-model,但在 Python 中广泛使用接口类和 IoC 看起来有点奇怪,而不是“pythonic”。但也许有人有更好的解释,为什么 IoC 没有在 Python 中广泛使用。
In Java IoC / DI is a very common practice which is extensively used in web applications, nearly all available frameworks and Java EE. On the other hand, there are also lots of big Python web applications, but beside of Zope (which I've heard should be really horrible to code) IoC doesn't seem to be very common in the Python world. (Please name some examples if you think that I'm wrong).
There are of course several clones of popular Java IoC frameworks available for Python, springpython for example. But none of them seems to get used practically. At least, I've never stumpled upon a Django or sqlalchemy+<insert your favorite wsgi toolkit here>
based web application which uses something like that.
In my opinion IoC has reasonable advantages and would make it easy to replace the django-default-user-model for example, but extensive usage of interface classes and IoC in Python looks a bit odd and not »pythonic«. But maybe someone has a better explanation, why IoC isn't widely used in Python.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(17)
我实际上并不认为 DI/IoC 在 Python 中不常见。然而,不常见的是 DI/IoC 框架/容器。
想一想:DI 容器是做什么的?它允许您
。我们有“连接在一起”和“运行时”的名称:
因此,DI 容器只不过是动态脚本语言的解释器。实际上,让我重新表述一下:典型的 Java/.NET DI 容器只不过是一个蹩脚的解释器,适用于一种非常糟糕的动态脚本语言,其语法非常丑陋,有时是基于 XML 的。
当你用 Python 编程时,当你有一种漂亮、出色的脚本语言可供使用时,为什么要使用一种丑陋、糟糕的脚本语言呢?实际上,这是一个更普遍的问题:当您使用几乎任何语言进行编程时,如果您可以使用 Jython 和 IronPython,为什么还要使用丑陋、糟糕的脚本语言呢?
因此,回顾一下:出于完全相同的原因,DI/IoC 的实践在 Python 中与在 Java 中同样重要。然而,DI/IoC 的实现是内置于语言中的,而且通常非常轻量,以至于完全消失了。
(这里有一个简短的类比:在汇编中,子程序调用是一件非常重要的事情 - 你必须将局部变量和寄存器保存到内存中,将返回地址保存在某处,将指令指针更改为你正在调用的子例程,安排它在完成时以某种方式跳回您的子例程,将参数放在被调用者可以找到它们的地方,依此类推:在汇编中,“子例程调用”是一种设计模式,并且在出现类似的语言之前。 Fortran 内置了子例程调用,人们正在构建自己的“子例程框架”,你会说子例程调用在 Python 中“不常见”,只是因为你不使用子例程框架吗?)
顺便说一句:举个例子。看起来像是将 DI 推向合乎逻辑的结论,看看 Gilad Bracha 的 Newspeak 编程语言 及其关于该主题的著作:
I don't actually think that DI/IoC are that uncommon in Python. What is uncommon, however, are DI/IoC frameworks/containers.
Think about it: what does a DI container do? It allows you to
We have names for "wiring together" and "at runtime":
So, a DI container is nothing but an interpreter for a dynamic scripting language. Actually, let me rephrase that: a typical Java/.NET DI container is nothing but a crappy interpreter for a really bad dynamic scripting language with butt-ugly, sometimes XML-based, syntax.
When you program in Python, why would you want to use an ugly, bad scripting language when you have a beautiful, brilliant scripting language at your disposal? Actually, that's a more general question: when you program in pretty much any language, why would you want to use an ugly, bad scripting language when you have Jython and IronPython at your disposal?
So, to recap: the practice of DI/IoC is just as important in Python as it is in Java, for exactly the same reasons. The implementation of DI/IoC however, is built into the language and often so lightweight that it completely vanishes.
(Here's a brief aside for an analogy: in assembly, a subroutine call is a pretty major deal - you have to save your local variables and registers to memory, save your return address somewhere, change the instruction pointer to the subroutine you are calling, arrange for it to somehow jump back into your subroutine when it is finished, put the arguments somewhere where the callee can find them, and so on. IOW: in assembly, "subroutine call" is a Design Pattern, and before there were languages like Fortran which had subroutine calls built in, people were building their own "subroutine frameworks". Would you say that subroutine calls are "uncommon" in Python, just because you don't use subroutine frameworks?)
BTW: for an example of what it looks like to take DI to its logical conclusion, take a look at Gilad Bracha's Newspeak Programming Language and his writings on the subject:
IoC 和 DI 在成熟的 Python 代码中非常常见。由于鸭子类型,您只是不需要框架来实现 DI。
最好的例子是如何使用
settings.py
设置 Django 应用程序:Django Rest Framework 大量使用 DI:
让我提醒一下(
IoC and DI are super common in mature Python code. You just don't need a framework to implement DI thanks to duck typing.
The best example is how you set up a Django application using
settings.py
:Django Rest Framework utilizes DI heavily:
Let me remind (source):
部分原因在于 Python 中模块系统的工作方式。您可以免费获得某种“单例”,只需从模块中导入它即可。在模块中定义对象的实际实例,然后任何客户端代码都可以导入它并实际获得一个工作的、完全构造/填充的对象。
这与 Java 不同,在 Java 中您不导入对象的实际实例。这意味着您始终必须自己实例化它们(或使用某种 IoC/DI 风格的方法)。您可以通过使用静态工厂方法(或实际的工厂类)来减轻必须自己实例化所有内容的麻烦,但每次仍然会产生实际创建新方法的资源开销。
Part of it is the way the module system works in Python. You can get a sort of "singleton" for free, just by importing it from a module. Define an actual instance of an object in a module, and then any client code can import it and actually get a working, fully constructed / populated object.
This is in contrast to Java, where you don't import actual instances of objects. This means you are always having to instantiate them yourself, (or use some sort of IoC/DI style approach). You can mitigate the hassle of having to instantiate everything yourself by having static factory methods (or actual factory classes), but then you still incur the resource overhead of actually creating new ones each time.
Django 充分利用了控制反转。例如,通过配置文件选择数据库服务器,然后框架向数据库客户端提供适当的数据库包装器实例。
不同之处在于 Python 有一流的类型。数据类型(包括类)本身就是对象。如果您希望某些东西使用特定的类,只需命名该类即可。例如:
后面的代码可以通过以下方式创建数据库接口:
Python 使用一两行普通代码来代替 Java 和 C++ 所需的样板工厂函数。这就是函数式编程与命令式编程的优势。
Django makes great use of inversion of control. For instance, the database server is selected by the configuration file, then the framework provides appropriate database wrapper instances to database clients.
The difference is that Python has first-class types. Data types, including classes, are themselves objects. If you want something to use a particular class, simply name the class. For example:
Later code can then create a database interface by writing:
Instead of the boilerplate factory functions that Java and C++ need, Python does it with one or two lines of ordinary code. This is the strength of functional versus imperative programming.
看来人们真的不明白依赖注入和控制反转意味着什么了。
使用控制反转的做法是让类或函数依赖于其他类或函数,但不要在类或函数代码中创建实例,最好将它们作为参数接收,这样就可以实现松耦合。这有很多好处,例如更多的可测试性和实现里氏替换原则。
您会看到,通过使用接口和注入,您的代码变得更加可维护,因为您可以轻松更改行为,因为您不必重写您的单行代码(可能是 DI 配置上的一两行)类来更改其行为,因为实现您的类正在等待的接口的类可以独立变化,只要它们遵循该接口即可。保持代码解耦和易于维护的最佳策略之一是至少遵循单一责任、替换和依赖倒置原则。
如果您可以在包中自己实例化一个对象并导入它以自己注入它,那么 DI 库有什么用呢?选择的答案是正确的,因为java没有过程部分(类之外的代码),所有这些都进入无聊的配置xml,因此需要一个类来实例化并以延迟加载方式注入依赖项,这样你就不会被吹走性能,而在 python 上,您只需在代码的“程序”(类外代码)部分中编写注入代码。
It seems that people really don't get what Dependency injection and inversion of control mean anymore.
The practice of using inversion of control is to have classes or functions that depend on other classes or functions, but instead of creating the instances whithin the class or function code it is better to receive them as parameters, so loose coupling can be achieved. That has many benefits as more testability and to achieve the liskov substitution principle.
You see, by working with interfaces and injections, your code gets more maintainable, since you can change the behavior easily, because you won't have to rewrite a single line of code (maybe a line or two on the DI configuration) of your class to change its behavior, since the classes that implement the interface your class is waiting for can vary independently as long as they follow the interface. One of the best strategies to keep code decoupled and easy to maintain is to follow at least the single responsibility, substitution and dependency inversion principles.
What's a DI library good for if you can instantiate an object yourself inside a package and import it to inject it yourself? The chosen answer is right, since java has no procedural sections (code outside of classes), all that goes into boring configuration xml's, hence the need of a class to instantiate and inject dependencies on a lazy load fashion so you don't blow away your performance, while on python you just code the injections in the "procedural" (code outside classes) sections of your code.
已经好几年没有使用 Python 了,但我想说,这更多的是因为它是一种动态类型语言,而不是其他任何东西。举一个简单的例子,在 Java 中,如果我想测试某些内容是否正确写入标准输出,我可以使用 DI 并传入任何 PrintStream 来捕获正在写入的文本并验证它。然而,当我在 Ruby 中工作时,我可以动态替换 STDOUT 上的“puts”方法来进行验证,将 DI 完全排除在外。如果我创建抽象的唯一原因是测试使用它的类(想想文件系统操作或 Java 中的时钟),那么 DI/IoC 会在解决方案中产生不必要的复杂性。
Haven't used Python in several years, but I would say that it has more to do with it being a dynamically typed language than anything else. For a simple example, in Java, if I wanted to test that something wrote to standard out appropriately I could use DI and pass in any PrintStream to capture the text being written and verify it. When I'm working in Ruby, however, I can dynamically replace the 'puts' method on STDOUT to do the verify, leaving DI completely out of the picture. If the only reason I'm creating an abstraction is to test the class that's using it (think File system operations or the clock in Java) then DI/IoC creates unnecessary complexity in the solution.
实际上,用 DI 编写足够干净和紧凑的代码是很容易的(我想知道,它会是/保持 pythonic 吗,但无论如何:)),例如我实际上更喜欢这种编码方式:
_
是的,这可以被视为参数化函数/类的简单形式,但它确实有效。所以,也许 Python 默认附带的电池在这里也足够了。
PS我还在动态地发布了这种幼稚方法的更大示例在 Python 中评估简单的布尔逻辑。
Actually, it is quite easy to write sufficiently clean and compact code with DI (I wonder, will it be/stay pythonic then, but anyway :) ), for example I actually perefer this way of coding:
_
Yes, this can be viewed as just a simple form of parameterizing functions/classes, but it does its work. So, maybe Python's default-included batteries are enough here too.
P.S. I have also posted a larger example of this naive approach at Dynamically evaluating simple boolean logic in Python.
IoC/DI 是一个设计概念,但不幸的是它经常被视为适用于某些语言(或打字系统)的概念。我很高兴看到依赖注入容器在 Python 中变得更加流行。 Spring 是一个超级框架,而且似乎是 Java 概念的直接移植,没有太多考虑“Python 方式”。
鉴于 Python 3 中的注释,我决定破解一个功能齐全但简单的依赖项注入容器: https:// /github.com/zsims/dic 。它基于 .NET 依赖注入容器中的一些概念(在我看来,如果您曾经在该领域玩过的话,这会很棒),但与 Python 概念发生了变化。
IoC/DI is a design concept, but unfortunately it's often taken as a concept that applies to certain languages (or typing systems). I'd love to see dependency injection containers become far more popular in Python. There's Spring, but that's a super-framework and seems to be a direct port of the Java concepts without much consideration for "The Python Way."
Given Annotations in Python 3, I decided to have a crack at a full featured, but simple, dependency injection container: https://github.com/zsims/dic . It's based on some concepts from a .NET dependency injection container (which IMO is fantastic if you're ever playing in that space), but mutated with Python concepts.
我认为由于 python 的动态特性,人们通常不会看到需要另一个动态框架。当类继承新式“对象”时,您可以动态创建一个新变量(https://wiki .python.org/moin/NewClassVsClassicClass)。
即
在普通的Python中:
但是看看 https://github.com/noodleflake/pyioc 这可能是你在寻找什么。
ie 在 pyioc 中
I think due to the dynamic nature of python people don't often see the need for another dynamic framework. When a class inherits from the new-style 'object' you can create a new variable dynamically (https://wiki.python.org/moin/NewClassVsClassicClass).
i.e.
In plain python:
However have a look at https://github.com/noodleflake/pyioc this might be what you are looking for.
i.e. In pyioc
pytest 装置全部基于 DI (源)
pytest fixtures all based on DI (source)
查看 FastAPI,它内置了依赖注入。例如:
Check out FastAPI, it has dependency injection built-in. For example:
我支持“Jörg W Mittag”的回答:“DI/IoC 的 Python 实现是如此轻量级,以至于它完全消失了”。
为了支持这一说法,请看一下著名的 Martin Fowler 从 Java 移植到 Python 的示例:Python:Design_Patterns:Inversion_of_Control
从上面的链接可以看到,Python中的一个“容器”可以用8行代码编写:
I back "Jörg W Mittag" answer: "The Python implementation of DI/IoC is so lightweight that it completely vanishes".
To back up this statement, take a look at the famous Martin Fowler's example ported from Java to Python: Python:Design_Patterns:Inversion_of_Control
As you can see from the above link, a "Container" in Python can be written in 8 lines of code:
我的 2 美分是,在大多数 Python 应用程序中你不需要它,即使你需要它,很多 Java 憎恨者(以及自认为是开发人员的无能的小提琴手)很可能认为它是不好的东西,只是因为它在 Java 中很流行。
当您拥有复杂的对象网络时,IoC 系统实际上很有用,其中每个对象可能是其他几个对象的依赖项,而反过来,它本身又依赖于其他对象。在这种情况下,您需要一次定义所有这些对象,并有一种机制根据尽可能多的隐式规则将它们自动组合在一起。如果您还需要由应用程序用户/管理员以简单的方式定义配置,那么这就是希望 IoC 系统能够从简单的 XML 文件(这将是配置)之类的文件中读取其组件的另一个原因。
典型的Python应用程序要简单得多,只是一堆脚本,没有那么复杂的架构。就我个人而言,我知道 IoC 实际上是什么(与那些在这里写下某些答案的人相反),并且在我有限的 Python 经验中我从未觉得需要它(而且我不会在任何地方使用 Spring,而不是当它的优点它给出的并不能证明其开发开销是合理的)。
也就是说,在某些 Python 情况下,IoC 方法实际上很有用,事实上,我在这里读到 Django 使用它。
上面同样的推理也适用于 Java 世界中的面向方面编程,不同之处在于 AOP 真正有价值的情况数量更加有限。
My 2cents is that in most Python applications you don't need it and, even if you needed it, chances are that many Java haters (and incompetent fiddlers who believe to be developers) consider it as something bad, just because it's popular in Java.
An IoC system is actually useful when you have complex networks of objects, where each object may be a dependency for several others and, in turn, be itself a dependant on other objects. In such a case you'll want to define all these objects once and have a mechanism to put them together automatically, based on as many implicit rules as possible. If you also have configuration to be defined in a simple way by the application user/administrator, that's an additional reason to desire an IoC system that can read its components from something like a simple XML file (which would be the configuration).
The typical Python application is much simpler, just a bunch of scripts, without such a complex architecture. Personally I'm aware of what an IoC actually is (contrary to those who wrote certain answers here) and I've never felt the need for it in my limited Python experience (also I don't use Spring everywhere, not when the advantages it gives don't justify its development overhead).
That said, there are Python situations where the IoC approach is actually useful and, in fact, I read here that Django uses it.
The same reasoning above could be applied to Aspect Oriented Programming in the Java world, with the difference that the number of cases where AOP is really worthwhile is even more limited.
您可以使用 Python 手动进行依赖项注入,但手动方法有其缺点:
要拥有这一切,我们需要一个依赖注入框架,例如这个 https://python-dependency-injector.ets-labs.org/index.html 似乎是Python最成熟的DI框架。
对于较小的应用程序,DI 容器不是必需的,对于任何具有几百行或更多代码的应用程序,DI 容器是保持代码可维护性的必备条件。
You can do dependency injection with Python manually, but manual approach has its downsides:
To have it all we NEED a dependency injection framework, for example this one https://python-dependency-injector.ets-labs.org/index.html seems to be the most mature DI framework for Python.
For smaller apps DI container is not necessary, for anything that has few hundred lines of code or more, DI container is a must have to keep your code maintaineable.
我同意 @Jorg 的观点,即 DI/IoC 在 Python 中是可能的、更容易的、甚至更美观。缺少的是支持它的框架,但也有一些例外。举几个我想到的例子:
Django 注释让您可以将自己的 Comment 类与自定义逻辑和表单连接起来。 [更多信息]
Django 让您使用自定义配置文件对象附加到您的用户模型。这并不完全是 IoC,但却是一个很好的方法。就我个人而言,我想像评论框架那样替换漏洞用户模型。 [更多信息]
I agree with @Jorg in the point that DI/IoC is possible, easier and even more beautiful in Python. What's missing is the frameworks supporting it, but there are a few exceptions. To point a couple of examples that come to my mind:
Django comments let you wire your own Comment class with your custom logic and forms. [More Info]
Django let you use a custom Profile object to attach to your User model. This is not completely IoC but is a good approach. Personally I'd like to replace the hole User model as the comments framework does. [More Info]
在我看来,依赖注入之类的东西是僵化且过于复杂的框架的症状。当代码主体变得太重而无法轻易更改时,您发现自己必须选择其中的一小部分,为它们定义接口,然后允许人们通过插入这些接口的对象来更改行为。这一切都很好,但最好首先避免这种复杂性。
这也是静态类型语言的症状。当您必须表达抽象的唯一工具是继承时,那么这几乎就是您在任何地方都使用的工具。话虽如此,C++ 非常相似,但从未像 Java 开发人员那样对到处的构建器和接口着迷。人们很容易对灵活和可扩展的梦想过于兴奋,但以编写 太多的通用代码几乎没有什么实际好处。我认为这是一个文化问题。
通常,我认为 Python 人们习惯于为工作选择正确的工具,这是一个连贯且简单的整体,而不是一个真正的工具(带有一千个可能的插件),它可以做任何事情,但提供一系列令人眼花缭乱的可能的配置排列。必要时仍然有可互换的部分,但由于鸭子类型的灵活性和语言的相对简单性,不需要定义固定接口的大形式主义。
In my opinion, things like dependency injection are symptoms of a rigid and over-complex framework. When the main body of code becomes much too weighty to change easily, you find yourself having to pick small parts of it, define interfaces for them, and then allowing people to change behaviour via the objects that plug into those interfaces. That's all well and good, but it's better to avoid that sort of complexity in the first place.
It's also the symptom of a statically-typed language. When the only tool you have to express abstraction is inheritance, then that's pretty much what you use everywhere. Having said that, C++ is pretty similar but never picked up the fascination with Builders and Interfaces everywhere that Java developers did. It is easy to get over-exuberant with the dream of being flexible and extensible at the cost of writing far too much generic code with little real benefit. I think it's a cultural thing.
Typically I think Python people are used to picking the right tool for the job, which is a coherent and simple whole, rather than the One True Tool (With A Thousand Possible Plugins) that can do anything but offers a bewildering array of possible configuration permutations. There are still interchangeable parts where necessary, but with no need for the big formalism of defining fixed interfaces, due to the flexibility of duck-typing and the relative simplicity of the language.
与 Java 中的强类型性质不同。 Python 的鸭子类型行为使得传递对象变得非常容易。
Java 开发人员专注于构建类结构和对象之间的关系,同时保持灵活性。 IoC 对于实现这一目标极其重要。
Python 开发人员专注于完成工作。他们只是在需要时连接课程。他们甚至不必担心课程的类型。只要能嘎嘎叫就是鸭子!这种性质没有给 IoC 留下任何空间。
Unlike the strong typed nature in Java. Python's duck typing behavior makes it so easy to pass objects around.
Java developers are focusing on the constructing the class strcuture and relation between objects, while keeping things flexible. IoC is extremely important for achieving this.
Python developers are focusing on getting the work done. They just wire up classes when they need it. They don't even have to worry about the type of the class. As long as it can quack, it's a duck! This nature leaves no room for IoC.