标签归档:inversion-of-control

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

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

在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中没有得到广泛使用。

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.


回答 0

我实际上并不认为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 BrachaNewspeak编程语言及其在该主题上的著作:

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

  1. wire together independent components into a complete application …
  2. … at runtime.

We have names for “wiring together” and “at runtime”:

  1. scripting
  2. dynamic

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:


回答 1

它的一部分是模块系统在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.


回答 2

IoC和DI在成熟的Python代码中非常常见。由于鸭子输入,您只需要一个框架来实现DI。

最好的示例是如何使用来设置Django应用程序settings.py

# 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大量利用了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

让我提醒一下(来源):

“依赖性注入”是5美分概念的25美元术语。依赖注入意味着给对象一个实例变量。[…]。

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:

# 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 utilizes DI heavily:

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

Let me remind (source):

“Dependency Injection” is a 25-dollar term for a 5-cent concept. […] Dependency injection means giving an object its instance variables. […].


回答 3

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

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

if config_dbms_name == 'postgresql':
    import psycopg
    self.database_interface = psycopg
elif config_dbms_name == 'mysql':
    ...

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

my_db_connection = self.database_interface()
# Do stuff with database.

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:

if config_dbms_name == 'postgresql':
    import psycopg
    self.database_interface = psycopg
elif config_dbms_name == 'mysql':
    ...

Later code can then create a database interface by writing:

my_db_connection = self.database_interface()
# Do stuff with database.

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.


回答 4

它看到人们真的不再得到依赖注入和控制反转意味着什么了。

使用控制反转的做法是让类或函数依赖于另一个类或函数,但是与其在函数代码的类中创建实例相比,不如在函数代码的类中创建实例,则最好将其作为参数来接收,因此可以简化松耦合。这具有许多优点,因为它们具有更高的可测试性以及归档liskov替换原理。

您会发现,通过使用接口和注入,代码可以更容易维护,因为您可以轻松更改行为,因为您不必重写一行代码(在DI配置中为一两行)类更改其行为,因为实现您的类正在等待的接口的类可以独立变化,只要它们遵循该接口即可。保持代码分离和易于维护的最佳策略之一是至少遵循单一的责任,替换和依赖关系反转原则。

如果您可以自己在包中实例化对象并将其导入以自己注入,那么DI库有什么用?选择的答案是正确的,因为Java没有过程部分(类之外的代码),所有这些都进入了无聊的配置xml,因此需要类来实例化和注入依赖于惰性加载方式的依赖项,因此您不会感到厌烦您的性能,而在python上,您只需在代码的“过程”(类外部代码)部分上编码注入

It seens that people really dont get what Dependency injection and inversion of control means anymore.

The practice of using inversion of control is to have classes or function that depends of another classes or functions, but instead of creating the instances whithin the class of function code it is better to receive it as a parameter, so loose coupling can be archieved. That has many benefits as more testability and to archieve the liskov substitution principle.

You see, by working with interfaces and injections, your code gets more maintanable, 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 it’s behavior, since the classes that implements 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 responsability, substitution and dependency inversion principles.

Whats a DI library good for if you can instantiate a 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 on the “procedural” (code outside classes) sections of your code


回答 5

几年来没有使用过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.


回答 6

实际上,用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) + "!"

_

>>greet("Peter")
Hello, dear Peter!
>>greet("Jack", rude)
Hello, Jack, you, moron!

是的,可以将其视为参数化函数/类的简单形式,但是它确实可以工作。因此,也许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:

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) + "!"

_

>>greet("Peter")
Hello, dear Peter!
>>greet("Jack", rude)
Hello, Jack, you, moron!

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.


回答 7

IoC / DI是一个设计概念,但不幸的是,它通常被视为适用于某些语言(或键入系统)的概念。我希望看到依赖注入容器在Python中变得越来越流行。有Spring,但是那是一个超级框架,似乎是Java概念的直接移植,而无需过多考虑“ Python方式”。

给定Python 3中的注释,我决定对功能齐全但简单的依赖项注入容器进行破解:https : //github.com/zsims/dic。它基于.NET依赖项注入容器中的一些概念(如果您曾经在该领域中玩,那么IMO就是一个不错的选择),但是却被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.


回答 8

我认为,由于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()

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

在pyooc

from libs.service_locator import ServiceLocator

#main.py
ServiceLocator.register(PostgresConnection)

#other.py
postgres_connection = ServiceLocator.resolve(PostgresConnection)
db_data = postgres_connection.fetchone()

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:

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

However have a look at https://github.com/noodleflake/pyioc this might be what you are looking for.

i.e. In pyioc

from libs.service_locator import ServiceLocator

#main.py
ServiceLocator.register(PostgresConnection)

#other.py
postgres_connection = ServiceLocator.resolve(PostgresConnection)
db_data = postgres_connection.fetchone()

回答 9

我支持“JörgW Mittag”的回答:“ DI / IoC的Python实现非常轻巧,因此完全消失了”。

为了支持这一说法,请看一下著名的Martin Fowler从Java移植到Python的示例: Python:Design_Patterns:Inversion_of_Control

从上面的链接中可以看到,Python中的“容器”可以用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

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:

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

回答 10

我的2cents是,在大多数Python应用程序中,您不需要它,即使您需要它,也有很多Java仇恨者(以及认为自己是开发人员的无能的提琴手)认为它不好,只是因为它在Java中很流行。

当您具有复杂的对象网络时,IoC系统实际上很有用,其中每个对象可能是其他几个对象的依赖项,而本身又是其他对象的依赖项。在这种情况下,您将希望一次定义所有这些对象,并具有一种机制,可以根据尽可能多的隐式规则将它们自动组合在一起。如果您还需要由应用程序用户/管理员以简单的方式定义配置,那么这就是希望IoC系统能够从简单的XML文件(即配置)中读取其组件的另一个原因。

没有这样复杂的体系结构,典型的Python应用程序要简单得多,只有一堆脚本。我个人知道IoC实际上是什么(与在此处写了某些答案的人相反),而我在有限的Python经验中从未感到过对IoC的需求(而且我并没有在所有地方都使用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.


回答 11

pytest夹具全部基于DI(来源

pytest fixtures all based on DI (source)


回答 12

我同意@Jorg的观点,那就是DI / IoC在Python中是可能的,更容易的,甚至更漂亮的。缺少的是支持它的框架,但是有一些exceptions。我想举几个例子:

  • Django注释使您可以使用自定义逻辑和表单来连接自己的Comment类。[更多信息]

  • Django允许您使用自定义Profile对象附加到您的User模型。这不是完全的IoC,而是一种很好的方法。我个人希望像注释框架那样替换空洞的User模型。[更多信息]

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]


回答 13

在我看来,诸如依赖注入之类的东西就是僵化和过度复杂框架的症状。当代码主体变得过于繁重而无法轻松更改时,您会发现自己不得不选择其中的一小部分,为它们定义接口,然后允许人们通过插入这些接口的对象来更改行为。一切都很好,但是最好首先避免这种复杂性。

这也是静态类型语言的症状。当您唯一需要表达抽象的工具是继承时,那么几乎到处都可以使用它。话虽这么说,C ++非常相似,但从未像Java开发人员那样在任何地方都对Builders和Interfaces着迷。梦想拥有灵活性和可扩展性很容易变得过于狂妄,而这样做的代价是编写太多的通用代码,却没有什么实际的好处。我认为这是文化的事情。

通常,我认为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.


回答 14

与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.