为什么IoC / DI在Python中不常见?
在Java中, IoC / DI是一种非常普遍的做法,广泛用于Web应用程序,几乎所有可用的框架和Java EE。 另一方面,也有很多大的Python Web应用程序,但除了Zope(我听说应该是非常糟糕的代码)之外,IoC在Python世界中似乎并不常见。 (如果你认为我错了,请列举一些例子)。
当然有几个stream行的Java IoC框架的克隆可用于Python,例如springpython 。 但是他们似乎都没有被实际使用。 至less,我从来没有在Django或sqlalchemy + <insert your favorite wsgi toolkit here>
基于Web的应用程序,它使用类似的东西。
在我看来,IoC具有合理的优势,例如可以很容易地replacedjango-default-user-model,但Python中接口类和IoC的广泛使用看起来有点奇怪,而不是pythonic。 但也许有人有一个更好的解释,为什么IoC没有广泛使用Python。
我并不认为DI / IoC在Python 中并不常见。 然而,不常见的是DI / IoC 框架/容器 。
想一想:DI容器是做什么的? 它允许你
- 将独立组件组合成完整的应用程序…
- 在运行时
我们有“连线”和“运行时”的名称:
- 脚本
- dynamic
所以,DI容器只不过是dynamic脚本语言的解释器。 实际上,让我重述一下:一个典型的Java / .NET DI容器只不过是一个糟糕的dynamic脚本语言的蹩脚的解释器,有时候却是基于XML的语法。
当你使用Python进行编程时,为什么当你拥有一个漂亮的,精彩的脚本语言的时候,你会使用一种丑陋的,不好的脚本语言? 实际上,这是一个更普遍的问题:当你用几乎任何语言编写程序时,为什么当你有Jython和IronPython的时候,你会用一个丑陋的,不好的脚本语言?
因此,总结一下:出于完全相同的原因,DI / IoC的实践在Python中与在Java中一样重要。 然而,DI / IoC的实现是build立在语言之上的,而且通常是如此轻量级以至于完全消失。
(下面简单地进行比较:在汇编中,子程序调用是一个非常重要的事情 – 必须将本地variables和寄存器保存到内存中,将地址保存到某处,将指令指针更改为所调用的子例程,安排它以某种方式跳回到你的子程序完成时,将参数放在被调用者可以find它们的地方,等等。IOW:在汇编中,“子程序调用”是一个devise模式,并且之前有像那些内置子程序调用的Fortran,人们正在构build他们自己的“子程序框架”。你会说Python的子程序调用是“不常见的”,因为你不使用子程序框架?)
顺便说一句:看看DI的逻辑结论是什么样的,看看Gilad Bracha的新闻编程语言和他的着作:
- 被认为是有害的build设者
- 注射死刑
- 禁止import ( 续 )
Django很好地利用了控制的倒置。 例如,数据库服务器由configuration文件select,然后框架为数据库客户机提供适当的数据库包装器实例。
不同的是,Python有一stream的types。 数据types(包括类)本身就是对象。 如果你想要某个特定的类,可以简单地命名这个类。 例如:
if config_dbms_name == 'postgresql': import psycopg self.database_interface = psycopg elif config_dbms_name == 'mysql': ...
稍后的代码可以通过编写来创build一个数据库接口:
my_db_connection = self.database_interface() # Do stuff with database.
Python不用Java和C ++需要的样板工厂函数,而是用一行或两行普通代码来完成它。 这是function与命令式编程的优势。
其中的一部分是模块系统在Python中的工作方式。 你可以免费获得一种“单身人士”,只需从模块中导入即可。 在一个模块中定义一个对象的实际实例,然后任何客户端代码都可以导入它,并且实际上得到一个工作的,完全构build/填充的对象。
这与Java不同,它不导入实际的对象实例。 这意味着你总是要自己实例化它们(或者使用某种IoC / DI风格的方法)。 你可以通过使用静态工厂方法(或者实际的工厂类)来减轻必须自己实例化所有事情的麻烦,但是每次都会产生实际创build新的资源的开销。
在过去的几年中没有使用过Python,但是我想说,它是一个dynamictypes语言,而不是其他任何东西。 举一个简单的例子,在Java中,如果我想testing某些东西写到标准输出,我可以使用DI并传入任何PrintStream来捕获正在写入的文本并进行validation。 但是,当我在Ruby中工作时,我可以dynamic地replaceSTDOUT上的“puts”方法来执行validation,将DI完全从图片中删除。 如果我创build抽象的唯一原因是testing正在使用它的类(考虑文件系统操作或Java中的时钟),那么DI / IoC会在解决scheme中造成不必要的复杂性。
我回到“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
IoC / DI是一个devise概念,但不幸的是,它经常被当作适用于某些语言(或打字系统)的概念。 我很高兴看到dependency injection容器在Python中变得更加stream行。 有Spring,但是这是一个超级框架,似乎是Java概念的一个直接的端口,没有太多的“Python方式”的考虑。
在Python 3中给出Annotations,我决定对一个function齐全但简单的dependency injection容器进行破解: https : //github.com/zsims/dic 。 它基于.NETdependency injection容器的一些概念(如果你曾经在这个空间中玩过,那么IMO是非常棒的),但是却被Python的概念所突变。
实际上,用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!
是的,这可以被看作是参数化函数/类的简单forms,但它的工作。 所以,也许Python的默认包含的电池也足够了。
PS我也发布了这个朴素的方法在Python中dynamic评估简单的布尔逻辑的更大的例子。
我认为由于Python的dynamic性,人们并不经常看到需要另一个dynamic框架。 当一个类inheritance新的“对象”types时,你可以dynamic地创build一个新的variables( 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()
在我看来,像dependency injection这样的东西是僵化和过于复杂的框架的症状。 当代码的主体变得太重而不易改变时,你会发现自己不得不从中select一小部分,为它们定义接口,然后允许人们通过插入这些接口的对象来改变行为。 这一切都很好,但最好先避免这种复杂性。
这也是静态types语言的症状。 当你必须expression抽象的唯一工具是inheritance,那么这就是你在任何地方使用的东西。 话虽如此,C ++是非常相似的,但从来没有拿起Java开发人员的build设者和接口的魅力。 以编写太多通用代码而没有多less实际利益为代价的灵活性和可扩展性的梦想容易过度繁荣。 我认为这是一个文化的东西。
通常情况下,我认为Python人员习惯于为工作select恰当的工具,这是一个连贯而简单的整体,而不是一个真正的工具(带有千个可能的插件),除了提供令人眼花缭乱的可能configuration排列。 在必要的地方,仍然有可互换的部分,但由于鸭式的灵活性和语言的相对简单性,不需要定义固定界面的大forms。
与Java中的强types本质不同。 Python的鸭子打字行为使得它很容易传递物体。
Java开发人员着重于构build类之间的结构和关系,同时保持事物的灵活性。 IoC对于实现这一点非常重要。
Python开发人员正专注于完成工作。 他们只是在需要的时候连线上课。 他们甚至不必担心class级的types。 只要它可以嘎嘎,这是一只鸭子! 这种性质让IoC没有余地。
我同意@Jorg的观点,即DI / IoC在Python中可能更容易,更容易,更美观。 缺less的是支持它的框架,但也有一些例外。 指出一些我脑海中出现的例子:
-
Django的评论让你用自定义的逻辑和表单连接你自己的Comment类。 [更多信息]
-
Django让你使用一个自定义Profile对象附加到你的用户模型。 这不完全是IoC,但是是一个好方法。 就个人而言,我想要像评论框架那样replace漏洞用户模型。 [更多信息]