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

时间:2010-03-17 11:10:34

标签: python design-patterns dependency-injection inversion-of-control architecture

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

当然,有几种流行的Java IoC框架克隆可用于Python,例如springpython。但它们似乎都没有被实际使用。至少,我从来没有弄清楚使用类似内容的基于Djangosqlalchemy + <insert your favorite wsgi toolkit here>的网络应用程序。

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

18 个答案:

答案 0 :(得分:177)

我实际上并不认为DI / IoC在Python中是 不常见的。然而, 不常见的是DI / IoC 框架/容器

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

  1. 将独立组件连接到一个完整的应用程序中......
  2. ...在运行时。
  3. 我们有“一起布线”和“在运行时”的名称:

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

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

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

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

      顺便说一句:有关将DI纳入其逻辑结论的示例,请查看Gilad BrachaNewspeak Programming Language以及他关于该主题的着作:

答案 1 :(得分:44)

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

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

答案 2 :(得分:36)

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 ++需要的样板工厂函数,而是使用一行或两行普通代码。这是功能与命令式编程的强项。

答案 3 :(得分:19)

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',
    }
}

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

让我提醒(source):

  

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

答案 4 :(得分:12)

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

答案 5 :(得分:9)

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

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

答案 6 :(得分: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) + "!"

_

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

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

P.S。我还在Dynamically evaluating simple boolean logic in Python发布了这个天真的方法的一个更大的例子。

答案 7 :(得分:7)

人们真的不知道依赖注入和控制反转意味着什么。

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

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

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

答案 8 :(得分:5)

我支持“JörgWMittag”回答:“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

答案 9 :(得分:4)

我认为由于python的动态特性,人们常常不再需要另一个动态框架。当一个类继承自新风格的对象时#39;您可以动态创建一个新变量(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这可能就是你要找的东西。

即。在pyioc

from libs.service_locator import ServiceLocator

#main.py
ServiceLocator.register(PostgresConnection)

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

答案 10 :(得分:2)

我的2个问题是,在大多数Python应用程序中,你不需要它,即使你需要它,很可能是许多Java仇恨者(以及那些相信是开发人员的无能的小提琴手)认为它是坏事,只是因为它在Java中很受欢迎。

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

典型的Python应用程序更简单,只是一堆脚本,没有这么复杂的架构。我个人意识到IoC实际上是什么(与那些在这里写下某些答案的人相反)并且在我有限的Python体验中我从来没有感觉到它的需要(我也不会使用Spring)无处不在,而不是当它带来的优势不能证明其开发成本合理时。)

也就是说,在Python情况下,IoC方法实际上很有用,事实上,我在这里读到Django使用它。

上述相同的推理可以应用于Java世界中的面向方面编程,不同之处在于AOP真正值得的案例数量更加有限。

答案 11 :(得分:1)

pytest灯具全部基于DI(source

答案 12 :(得分:0)

您可以手动使用 Python 进行依赖注入,但手动方法有其缺点:

  • 大量样板代码进行接线。您可以使用 Python 的动态特性进行注入,但随后您将失去 IDE 支持(例如 PyCharm 中的 Ctrl+Space),并且您使代码更难理解和调试
  • 没有标准:每个程序员都有自己解决相同问题的方法,这会导致重新发明轮子,理解彼此的代码很快就会变成一种痛苦。依赖注入库为插件提供了简单的框架

为了拥有这一切,我们需要一个依赖注入框架,例如这个 https://python-dependency-injector.ets-labs.org/index.html 似乎是 Python 最成熟的 DI 框架。

对于较小的应用程序,DI 容器不是必需的,对于任何具有几百行或更多代码的应用程序,DI 容器是保持代码可维护性的必要条件。

答案 13 :(得分:0)

IoC 容器主要使用 **kwargs

class A:
    def __init__(self, **kwargs):
        print(kwargs)

Class B:
    pass

Class C:
    pass

Ainstance = A(b=B, c=C)

答案 14 :(得分:0)

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

from fastapi import Depends, FastAPI

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

app = FastAPI()

@app.get("/items")
def get_items(db=Depends(get_db)):
    return db.get_items()

答案 15 :(得分:-1)

我同意@Jorg的观点,即DI / IoC在Python中可行,更简单,更美观。缺少的是支持它的框架,但也有一些例外。要指出一些我想到的例子:

  • Django评论允许您使用自定义逻辑和表单连接自己的Comment类。 [More Info]

  • Django允许您使用自定义Profile对象附加到您的User模型。这不是完全IoC,但是一个很好的方法。就个人而言,我想像评论框架那样替换漏洞用户模型。 [More Info]

答案 16 :(得分:-3)

与Java中的强类型性质不同。 Python的duck typing行为使得传递对象变得如此容易。

Java开发人员专注于构建类strcuture和对象之间的关系,同时保持灵活性。 IoC对于实现这一点非常重要。

Python开发人员专注于完成工作。他们只是在需要时连接课程。他们甚至不必担心班级的类型。只要它可以嘎嘎叫,它就是一只鸭子!这种性质为IoC留下了空间。

答案 17 :(得分:-3)

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

这也是静态类型语言的症状。当你必须表达抽象的唯一工具是继承时,那几乎就是你在任何地方使用的工具。话虽如此,C ++非常相似,但从未在Java开发人员所做的任何地方发现构建器和接口的魅力。以编写far too much generic code with little real benefit为代价,以灵活和可扩展的梦想容易过度繁荣。我认为这是一种文化事物。

通常我认为Python人习惯于为工作挑选合适的工具,这是一个连贯而简单的整体,而不是一个真正的工具(有一千个可能的插件),可以做任何事情,但提供一个令人眼花缭乱的阵列可能的配置排列。在必要时仍然可以互换部分,但由于鸭子类型的灵活性和语言的相对简单性,不需要定义固定接口的大型形式。