Why is IoC / DI not common in Python?
在Java中,IOC/DI是一种非常普遍的实践,广泛应用于Web应用程序中,几乎所有可用的框架和JavaEE。另一方面,也有许多大型的python web应用程序,但是除了zope(我听说这对代码来说应该很可怕)之外,ioc在python世界中似乎并不常见。(如果你认为我错了,请举出一些例子)。
当然,对于Python,SpRypyPython,有几种流行的Java IOC框架的克隆。但它们似乎都没有实际应用。至少,我从来没有遇到过Django或sqlacalchemy+
在我看来,ioc有合理的优势,可以很容易地替换django默认的用户模型,但是大量使用接口类和ioc在python中看起来有点奇怪,不是吗?Python?但是也许有人有更好的解释,为什么ioc没有在python中广泛使用。
实际上,我并不认为DI/IOC在Python中如此罕见。然而,不常见的是DI/IOC框架/容器。
考虑一下:DI容器做什么?它允许你
我们有"连接在一起"和"运行时"的名称:
因此,DI容器只是动态脚本语言的解释器。实际上,让我重申一下:典型的Java/.net DI容器只不过是一个糟糕的动态脚本语言的蹩脚的解释器,它具有难看的、有时基于XML的语法。
当你用python编程时,当你有一个漂亮的,出色的脚本语言在你的处理下,为什么你要使用一个丑陋的,糟糕的脚本语言?实际上,这是一个更普遍的问题:当你用几乎任何一种语言编程时,当你有Jython和Ironpython可供使用时,为什么要使用一种丑陋的、糟糕的脚本语言?
因此,要重申:DI/IOC的实践在Python中与Java同样重要,原因完全相同。然而,DI/IOC的实现是内置在语言中的,并且通常非常轻,以至于它完全消失了。
(这里有一个简单的类比:在汇编中,一个子程序调用是一个相当重要的事务——你必须将局部变量和寄存器保存到内存中,将返回地址保存到某个地方,将指令指针更改为你正在调用的子程序,安排它在完成后以某种方式跳转回你的子程序,将参数被叫方可以找到的地方,等等。IOW:在汇编中,"子程序调用"是一种设计模式,在像Fortran这样的语言内置了子程序调用之前,人们正在构建自己的"子程序框架"。您是否会说,在Python中,子例程调用"不常见",仅仅是因为您不使用子例程框架?)
顺便说一句:举一个将di带到逻辑结论的例子,看看Gilad Bracha的Newspeak编程语言和他关于这个主题的著作:
- 施工人员认为有害
- 注射死刑
- 禁止进口(续)
部分原因是模块系统在Python中的工作方式。只需从模块导入,就可以免费获得一种"singleton"。在模块中定义一个对象的实际实例,然后任何客户机代码都可以导入该实例,并实际获得一个工作的、完全构造/填充的对象。
这与Java相反,在这里您不导入对象的实际实例。这意味着您必须自己实例化它们(或者使用某种IOC/DI风格的方法)。您可以通过使用静态工厂方法(或实际的工厂类)来减轻必须自己实例化所有东西的麻烦,但是您仍然要承担每次实际创建新方法的资源开销。
Django充分利用了控制反转。例如,数据库服务器由配置文件选择,然后框架为数据库客户机提供适当的数据库包装实例。
区别在于Python有一流的类型。数据类型(包括类)本身就是对象。如果您想使用某个特定的类,只需命名该类即可。例如:
1 2 3 4 5 | if config_dbms_name == 'postgresql': import psycopg self.database_interface = psycopg elif config_dbms_name == 'mysql': ... |
稍后的代码可以通过编写以下内容来创建数据库接口:
1 2 | my_db_connection = self.database_interface() # Do stuff with database. |
代替Java和C++需要的样板工厂功能,Python用一行或两行普通代码来完成它。这是函数式编程与命令式编程的优势所在。
ioc和di在成熟的python代码中非常常见。由于duck类型化,您不需要框架来实现DI。
最好的例子是如何使用
1 2 3 4 5 6 7 8 9 10 11 | # 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框架大量使用DI:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
让我提醒一下(资料来源):
"Dependency Injection" is a 25-dollar term for a 5-cent concept. [...] Dependency injection means giving an object its instance variables. [...].
已经有几年没有使用过python了,但我想说的是,它与动态类型语言的关系比其他任何语言都要大。对于一个简单的例子,在Java中,如果我想测试一些适当地写入标准的东西,我可以使用任何打印流中的DI和PASS来捕获正在编写和验证的文本。但是,当我在Ruby中工作时,我可以在stdout上动态替换"puts"方法来进行验证,从而使di完全不在图片中。如果我创建抽象的唯一原因是测试使用它的类(思考文件系统操作或爪哇中的时钟),那么DI/IOC会在解决方案中造成不必要的复杂性。
IOC/DI是一个设计概念,但不幸的是,它常常被视为一个适用于某些语言(或打字系统)的概念。我希望看到依赖注入容器在Python中变得更加流行。这里有Spring,但这是一个超级框架,它似乎是Java概念的直接端口,而不用太多地考虑"Python方式"。
考虑到python 3中的注释,我决定破解一个功能完整但简单的依赖注入容器:https://github.com/zsims/dic。它基于来自.NET依赖注入容器的一些概念(如果您在这个空间中玩过,IMO会非常棒),但会随着python概念而改变。
它看到人们真的不知道依赖注入和控制反转意味着什么。
使用控制反转的实践是拥有依赖于其他类或函数的类或函数,但与其创建函数代码类中的实例,不如将其作为参数接收,这样可以实现松散耦合。这有许多好处,因为更多的可测试性和archieve的liskov替代原则。
你看,通过使用接口和注入,你的代码变得更易于维护,因为你可以很容易地改变行为,因为你不需要重写你的类的一行代码(可能是DI配置上的一两行代码)来改变它的行为,因为实现你的类正在等待的接口的类可以变量y只要它们遵循接口就可以独立地执行。保持代码解耦和易于维护的最佳策略之一是至少遵循单一的响应性、替换和依赖倒置原则。
如果您可以自己在包中实例化一个对象并导入它以自己注入它,那么DI库有什么用呢?选择的答案是正确的,因为Java没有程序段(类之外的代码),所有这些都进入了乏味的配置XML,因此需要一个类来实例化并以懒惰的加载方式注入依赖关系,这样就不会影响您的性能,而在Python上,只需对"程序"("C"之外的代码)进行编码。lasses)代码部分
事实上,用DI编写足够干净和紧凑的代码是很容易的(我想知道,那时会不会是/保持pythonic,但无论如何是:)),例如,我实际上采用了这种编码方式:
1 2 3 4 5 6 7 8 | 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) +"!" |
γ
1 2 3 4 | >>greet("Peter") Hello, dear Peter! >>greet("Jack", rude) Hello, Jack, you, moron! |
是的,这可以看作是参数化函数/类的一种简单形式,但它确实起作用。所以,也许python的默认电池在这里也足够了。
另外,在动态评估Python中的简单布尔逻辑时,我还发布了这种幼稚方法的更大示例。
我回来了吗?rg w mittag"答案:"di/ioc的python实现非常轻,以至于完全消失了。
为了支持这个语句,看看著名的Martin Fowler例子,它从Java移植到Python:Python:Debug模式:
从上面的链接可以看到,python中的"容器"可以用8行代码编写:
1 2 3 4 5 6 7 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 |
我认为,由于Python的动态特性,人们并不经常看到需要另一个动态框架。当一个类从新样式的"对象"继承时,您可以动态地创建一个新变量(https://wiki.python.org/moin/newclassvsclassicclass)。
即在纯Python中:
1 2 3 4 5 6 7 8 9 10 11 | #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,这可能是您要找的。
即在PYOC中
1 2 3 4 5 6 7 8 | from libs.service_locator import ServiceLocator #main.py ServiceLocator.register(PostgresConnection) #other.py postgres_connection = ServiceLocator.resolve(PostgresConnection) db_data = postgres_connection.fetchone() |
我的2美分是,在大多数Python应用程序中,你不需要它,即使你需要它,也有可能是许多Java仇恨者(以及那些认为是开发者的无能的小提琴手)认为它是不好的东西,只是因为它在Java中很受欢迎。
当您拥有复杂的对象网络时,IOC系统实际上是有用的,其中每个对象可能是多个其他对象的依赖关系,反过来,它本身也依赖于其他对象。在这种情况下,您将希望一次性定义所有这些对象,并有一种机制根据尽可能多的隐式规则自动将它们组合在一起。如果您还需要由应用程序用户/管理员以简单的方式定义配置,这也是希望IOC系统能够从简单的XML文件(即配置)之类的文件中读取其组件的另一个原因。
典型的Python应用程序要简单得多,只是一堆脚本,没有如此复杂的体系结构。就我个人而言,我知道IOC实际上是什么(与那些在这里写下某些答案的人相反),而且在我有限的python体验中,我从来没有感觉到它的必要性(我也不在任何地方使用Spring,而不是它所提供的优势不足以证明它的开发开销)。
也就是说,在Python的情况下,IOC方法实际上是有用的,实际上,我在这里读到了Django使用它的消息。
上面的推理可以应用于Java世界中的面向方面编程,不同之处在于AOP真正值得的情况的数量甚至更有限。
我同意@jorg的观点,即DI/IOC在Python中是可能的、更容易的,甚至更漂亮。缺少的是支持它的框架,但也有一些例外。我想举几个例子:
django comments允许您使用自定义逻辑和表单连接自己的注释类。[更多信息]
Django允许您使用自定义配置文件对象附加到您的用户模型。这不是完全的国际奥委会,但是一个很好的方法。就我个人而言,我想像评论框架那样取代hole用户模型。[更多信息]
与Java中的强类型性质不同。python的duck类型行为使得传递对象变得非常容易。
Java开发人员专注于构造对象的类结构和关系,同时保持事物的灵活性。国际奥委会对于实现这一目标非常重要。
Python开发人员正专注于完成工作。他们只是在需要的时候把课程连接起来。他们甚至不用担心这门课的类型。只要它能嘎嘎叫,它就是一只鸭子!这种性质没有给国际奥委会留下空间。
在我看来,依赖注入之类的东西是僵化和过于复杂的框架的症状。当代码的主体变得太重而无法轻易更改时,您会发现自己必须选择其中的一小部分,为它们定义接口,然后允许人们通过插入这些接口的对象更改行为。这一切都很好,但首先最好避免这种复杂性。
它也是静态类型语言的症状。当您必须表达抽象的唯一工具是继承时,那么这几乎就是您在任何地方使用的工具。话虽如此,C++非常相似,但从未对Java开发人员所做的任何地方的建设者和接口着迷。人们很容易克服这样一个梦想:灵活性和可扩展性,代价是编写太多的通用代码,而实际的好处却很小。我认为这是一种文化。
通常我认为python的人习惯于为工作选择合适的工具,这是一个连贯而简单的整体,而不是一个真正的工具(有上千个可能的插件),它可以做任何事情,但提供了一系列令人困惑的可能的配置排列。在必要的地方仍然有可互换的部分,但是由于duck类型的灵活性和语言的相对简单性,不需要定义固定接口的大型形式主义。