标签归档:architecture

在Python中构建最小的插件架构

问题:在Python中构建最小的插件架构

我有一个用Python编写的应用程序,供相当技术的读者(科学家)使用。

我正在寻找一种使用户可以扩展应用程序的好方法,即脚本/插件体系结构。

我正在寻找轻巧的东西。大多数脚本或插件都不会由第三方开发和分发并安装,而是会在几分钟内被用户搅动,以自动执行重复任务,添加对文件格式的支持,等等。因此,插件应该具有绝对的最小模板代码,并且除了复制到文件夹外,不需要“安装”(因此,诸如setuptools入口点之类的东西,或者Zope插件架构似乎太多了。)

是否已经有类似这样的系统,或者是否有实施类似方案的项目,我应该寻求灵感/启发?

I have an application, written in Python, which is used by a fairly technical audience (scientists).

I’m looking for a good way to make the application extensible by the users, i.e. a scripting/plugin architecture.

I am looking for something extremely lightweight. Most scripts, or plugins, are not going to be developed and distributed by a third-party and installed, but are going to be something whipped up by a user in a few minutes to automate a repeating task, add support for a file format, etc. So plugins should have the absolute minimum boilerplate code, and require no ‘installation’ other than copying to a folder (so something like setuptools entry points, or the Zope plugin architecture seems like too much.)

Are there any systems like this already out there, or any projects that implement a similar scheme that I should look at for ideas / inspiration?


回答 0

基本上,我的目录是一个名为“插件”的目录,主应用程序可以对其进行轮询,然后使用imp.load_module拾取文件,查找可能带有模块级配置参数的知名入口点,然后从那里进入。我使用文件监视功能进行一定程度的动态处理,使插件处于活动状态,但这很不错。

当然,任何伴随着“我不需要[大,复杂的东西] X;我只想要轻量级的东西”的需求都会冒着重新实现X一次发现的需求的风险。但这并不是说您无法获得任何乐趣:)

Mine is, basically, a directory called “plugins” which the main app can poll and then use imp.load_module to pick up files, look for a well-known entry point possibly with module-level config params, and go from there. I use file-monitoring stuff for a certain amount of dynamism in which plugins are active, but that’s a nice-to-have.

Of course, any requirement that comes along saying “I don’t need [big, complicated thing] X; I just want something lightweight” runs the risk of re-implementing X one discovered requirement at a time. But that’s not to say you can’t have some fun doing it anyway :)


回答 1

module_example.py

def plugin_main(*args, **kwargs):
    print args, kwargs

loader.py

def load_plugin(name):
    mod = __import__("module_%s" % name)
    return mod

def call_plugin(name, *args, **kwargs):
    plugin = load_plugin(name)
    plugin.plugin_main(*args, **kwargs)

call_plugin("example", 1234)

它肯定是“最小的”,它绝对没有错误检查,可能有无数的安全问题,不是很灵活-但它应该向您展示Python中的插件系统可以多么简单。

你可能要考虑的小鬼模块也一样,虽然你可以做很多事只是__import__os.listdir和一些字符串操作。

module_example.py:

def plugin_main(*args, **kwargs):
    print args, kwargs

loader.py:

def load_plugin(name):
    mod = __import__("module_%s" % name)
    return mod

def call_plugin(name, *args, **kwargs):
    plugin = load_plugin(name)
    plugin.plugin_main(*args, **kwargs)

call_plugin("example", 1234)

It’s certainly “minimal”, it has absolutely no error checking, probably countless security problems, it’s not very flexible – but it should show you how simple a plugin system in Python can be..

You probably want to look into the imp module too, although you can do a lot with just __import__, os.listdir and some string manipulation.


回答 2

看一下现有插件框架/库的概述,这是一个很好的起点。我非常喜欢yapsy,但这取决于您的用例。

Have a look at at this overview over existing plugin frameworks / libraries, it is a good starting point. I quite like yapsy, but it depends on your use-case.


回答 3

尽管这个问题确实很有趣,但我认为如果没有更多细节,很难回答。这是什么样的应用程序?它有GUI吗?它是命令行工具吗?一组脚本?具有唯一入口点的程序,等等。

鉴于我所掌握的信息很少,我将以非常通用的方式回答。

您必须添加插件是什么意思?

  • 您可能必须添加一个配置文件,该文件将列出要加载的路径/目录。
  • 另一种方式是说“将加载该插件/目录中的所有文件”,但这不方便要求用户在文件之间移动。
  • 最后一个中间选项是要求所有插件都在同一插件/文件夹中,然后使用配置文件中的相对路径激活/停用它们。

在纯代码/设计实践中,您必须明确确定要用户扩展的行为/特定操作。确定将始终被覆盖的公共入口点/一组功能,并确定这些操作中的组。完成此操作后,扩展应用程序应该很容易,

钩子的示例,该示例受MediaWiki(PHP,但语言真的很重要吗?)的启发:

import hooks

# In your core code, on key points, you allow user to run actions:
def compute(...):
    try:
        hooks.runHook(hooks.registered.beforeCompute)
    except hooks.hookException:
        print('Error while executing plugin')

    # [compute main code] ...

    try:
        hooks.runHook(hooks.registered.afterCompute)
    except hooks.hookException:
        print('Error while executing plugin')

# The idea is to insert possibilities for users to extend the behavior 
# where it matters.
# If you need to, pass context parameters to runHook. Remember that
# runHook can be defined as a runHook(*args, **kwargs) function, not
# requiring you to define a common interface for *all* hooks. Quite flexible :)

# --------------------

# And in the plugin code:
# [...] plugin magic
def doStuff():
    # ....
# and register the functionalities in hooks

# doStuff will be called at the end of each core.compute() call
hooks.registered.afterCompute.append(doStuff)

另一个例子,灵感来自水银。在这里,扩展仅将命令添加到hg命令行可执行文件,从而扩展了行为。

def doStuff(ui, repo, *args, **kwargs):
    # when called, a extension function always receives:
    # * an ui object (user interface, prints, warnings, etc)
    # * a repository object (main object from which most operations are doable)
    # * command-line arguments that were not used by the core program

    doMoreMagicStuff()
    obj = maybeCreateSomeObjects()

# each extension defines a commands dictionary in the main extension file
commands = { 'newcommand': doStuff }

对于这两种方法,您可能需要对扩展进行通用的初始化终结处理。您可以使用所有扩展程序都必须实现的通用接口(更适合第二种方法; Mercurial使用所有扩展程序都需要使用的reposetup(ui,repo)),也可以使用带有某种挂钩的方法hooks.setup钩子。

但是同样,如果您想要更有用的答案,则必须缩小问题的范围;)

While that question is really interesting, I think it’s fairly hard to answer, without more details. What sort of application is this? Does it have a GUI? Is it a command-line tool? A set of scripts? A program with an unique entry point, etc…

Given the little information I have, I will answer in a very generic manner.

What means do you have to add plugins?

  • You will probably have to add a configuration file, which will list the paths/directories to load.
  • Another way would be to say “any files in that plugin/ directory will be loaded”, but it has the inconvenient to require your users to move around files.
  • A last, intermediate option would be to require all plugins to be in the same plugin/ folder, and then to active/deactivate them using relative paths in a config file.

On a pure code/design practice, you’ll have to determine clearly what behavior/specific actions you want your users to extend. Identify the common entry point/a set of functionalities that will always be overridden, and determine groups within these actions. Once this is done, it should be easy to extend your application,

Example using hooks, inspired from MediaWiki (PHP, but does language really matters?):

import hooks

# In your core code, on key points, you allow user to run actions:
def compute(...):
    try:
        hooks.runHook(hooks.registered.beforeCompute)
    except hooks.hookException:
        print('Error while executing plugin')

    # [compute main code] ...

    try:
        hooks.runHook(hooks.registered.afterCompute)
    except hooks.hookException:
        print('Error while executing plugin')

# The idea is to insert possibilities for users to extend the behavior 
# where it matters.
# If you need to, pass context parameters to runHook. Remember that
# runHook can be defined as a runHook(*args, **kwargs) function, not
# requiring you to define a common interface for *all* hooks. Quite flexible :)

# --------------------

# And in the plugin code:
# [...] plugin magic
def doStuff():
    # ....
# and register the functionalities in hooks

# doStuff will be called at the end of each core.compute() call
hooks.registered.afterCompute.append(doStuff)

Another example, inspired from mercurial. Here, extensions only add commands to the hg commandline executable, extending the behavior.

def doStuff(ui, repo, *args, **kwargs):
    # when called, a extension function always receives:
    # * an ui object (user interface, prints, warnings, etc)
    # * a repository object (main object from which most operations are doable)
    # * command-line arguments that were not used by the core program

    doMoreMagicStuff()
    obj = maybeCreateSomeObjects()

# each extension defines a commands dictionary in the main extension file
commands = { 'newcommand': doStuff }

For both approaches, you might need common initialize and finalize for your extension. You can either use a common interface that all your extension will have to implement (fits better with second approach; mercurial uses a reposetup(ui, repo) that is called for all extension), or use a hook-kind of approach, with a hooks.setup hook.

But again, if you want more useful answers, you’ll have to narrow down your question ;)


回答 4

Marty Allchin的简单插件框架是我满足自己需求的基础。我真的建议您看一下它,如果您想要简单易行的内容,那真的是一个不错的开始。您也可以将其作为Django代码段找到。

Marty Allchin’s simple plugin framework is the base I use for my own needs. I really recommand to take a look at it, I think it is really a good start if you want something simple and easily hackable. You can find it also as a Django Snippets.


回答 5

我是一位退休的生物学家,负责处理数字微图,发现自己必须编写图像处理和分析程序包(从技术上来说不是库),才能在SGi机器上运行。我用C语言编写了代码,并使用Tcl作为脚本语言。这样的GUI就是使用Tk完成的。Tcl中出现的命令的格式为“ extensionName commandName arg0 arg1 … param0 param1 …”,即,用空格分隔的简单单词和数字。当Tcl看到“ extensionName”子字符串时,控制权传递给C包。依次通过lexer / parser(在lex / yacc中完成)运行命令,然后根据需要调用C例程。

可以通过GUI中的窗口逐个运行用于操作软件包的命令,但是批处理作业是通过编辑文本文件完成的,这些文本文件是有效的Tcl脚本;您将选择执行所需的文件级操作的模板,然后编辑一个副本以包含实际的目录和文件名以及package命令。它就像一种魅力。直到 …

1)世界转向PC,2)当Tcl顽强的组织能力开始成为真正的不便时,脚本的长度超过了500行。时间飞逝 …

我退休了,Python被发明了,它看起来像是Tcl的完美继承者。现在,我从未做过移植工作,因为我从未面临过在PC上编译(相当大的)C程序,用C包扩展Python以及在Python / Gt?/ Tk?/?中进行GUI的挑战。 ?但是,拥有可编辑模板脚本的旧想法似乎仍然可行。同样,以本机Python形式输入包命令也不会带来太大负担,例如:

packageName.command(arg0,arg1,…,param0,param1,…)

一些额外的圆点,括号和逗号,但这些不是最常用的。

我记得曾经看到有人在Python中完成了lex和yacc的版本(尝试:http : //www.dabeaz.com/ply/),所以如果仍然需要它们,那就可以了。

这种杂乱无章的要点是,在我看来,Python本身是科学家可以使用的理想“轻量级”前端。我很想知道您为什么不这样认为,我是认真的意思。


稍后添加:应用程序gedit预计会添加插件,并且它们的站点中有关于我在几分钟内发现的简单插件过程的最清晰的解释。尝试:

https://wiki.gnome.org/Apps/Gedit/PythonPluginHowToOld

我仍然想更好地了解您的问题。我不清楚您是1)希望科学家能够以各种方式简单地使用您的(Python)应用程序,还是2)是否允许科学家为您的应用程序添加新功能。选择#1是我们面对图像的情况,这导致我们使用通用脚本,我们对该脚本进行了修改以满足当前需求。是选择2使您想到插件的想法,还是应用程序的某些方面使向其发出命令不可行?

I am a retired biologist who dealt with digital micrograqphs and found himself having to write an image processing and analysis package (not technically a library) to run on an SGi machine. I wrote the code in C and used Tcl for the scripting language. The GUI, such as it was, was done using Tk. The commands that appeared in Tcl were of the form “extensionName commandName arg0 arg1 … param0 param1 …”, that is, simple space-separated words and numbers. When Tcl saw the “extensionName” substring, control was passed to the C package. That in turn ran the command through a lexer/parser (done in lex/yacc) and then called C routines as necessary.

The commands to operate the package could be run one by one via a window in the GUI, but batch jobs were done by editing text files which were valid Tcl scripts; you’d pick the template that did the kind of file-level operation you wanted to do and then edit a copy to contain the actual directory and file names plus the package commands. It worked like a charm. Until …

1) The world turned to PCs and 2) the scripts got longer than about 500 lines, when Tcl’s iffy organizational capabilities started to become a real inconvenience. Time passed …

I retired, Python got invented, and it looked like the perfect successor to Tcl. Now, I have never done the port, because I have never faced up to the challenges of compiling (pretty big) C programs on a PC, extending Python with a C package, and doing GUIs in Python/Gt?/Tk?/??. However, the old idea of having editable template scripts seems still workable. Also, it should not be too great a burden to enter package commands in a native Python form, e.g.:

packageName.command( arg0, arg1, …, param0, param1, …)

A few extra dots, parens, and commas, but those aren’t showstoppers.

I remember seeing that someone has done versions of lex and yacc in Python (try: http://www.dabeaz.com/ply/), so if those are still needed, they’re around.

The point of this rambling is that it has seemed to me that Python itself IS the desired “lightweight” front end usable by scientists. I’m curious to know why you think that it is not, and I mean that seriously.


added later: The application gedit anticipates plugins being added and their site has about the clearest explanation of a simple plugin procedure I’ve found in a few minutes of looking around. Try:

https://wiki.gnome.org/Apps/Gedit/PythonPluginHowToOld

I’d still like to understand your question better. I am unclear whether you 1) want scientists to be able to use your (Python) application quite simply in various ways or 2) want to allow the scientists to add new capabilities to your application. Choice #1 is the situation we faced with the images and that led us to use generic scripts which we modified to suit the need of the moment. Is it Choice #2 which leads you to the idea of plugins, or is it some aspect of your application that makes issuing commands to it impracticable?


回答 6

当我搜索Python Decorators时,发现了一个简单但有用的代码段。它可能不适合您的需求,但很有启发性。

Scipy高级Python#插件注册系统

class TextProcessor(object):
    PLUGINS = []

    def process(self, text, plugins=()):
        if plugins is ():
            for plugin in self.PLUGINS:
                text = plugin().process(text)
        else:
            for plugin in plugins:
                text = plugin().process(text)
        return text

    @classmethod
    def plugin(cls, plugin):
        cls.PLUGINS.append(plugin)
        return plugin


@TextProcessor.plugin
class CleanMarkdownBolds(object):
    def process(self, text):
        return text.replace('**', '')

用法:

processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")

When i searching for Python Decorators, found a simple but useful code snippet. It may not fit in your needs but very inspiring.

Scipy Advanced Python#Plugin Registration System

class TextProcessor(object):
    PLUGINS = []

    def process(self, text, plugins=()):
        if plugins is ():
            for plugin in self.PLUGINS:
                text = plugin().process(text)
        else:
            for plugin in plugins:
                text = plugin().process(text)
        return text

    @classmethod
    def plugin(cls, plugin):
        cls.PLUGINS.append(plugin)
        return plugin


@TextProcessor.plugin
class CleanMarkdownBolds(object):
    def process(self, text):
        return text.replace('**', '')

Usage:

processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")

回答 7

我很享受Pycon 2009上Andre Roberge博士就不同的插件体系结构进行的精彩讨论。他从一个非常简单的东西开始,很好地概述了实现插件的不同方法。

它可以作为播客(后面是对Monkey补丁的解释的第二部分),附带一系列六个博客条目

我建议您在做出决定之前先快速听一下。

I enjoyed the nice discussion on different plugin architectures given by Dr Andre Roberge at Pycon 2009. He gives a good overview of different ways of implementing plugins, starting from something really simple.

Its available as a podcast (second part following an explanation of monkey-patching) accompanied by a series of six blog entries.

I recommend giving it a quick listen before you make a decision.


回答 8

我来到这里寻找的是最小的插件体系结构,发现很多东西对我来说似乎太过分了。因此,我已经实现了Super Simple Python Plugins。要使用它,您需要创建一个或多个目录,并__init__.py在每个目录中放置一个特殊文件。导入这些目录将导致所有其他Python文件作为子模块加载,并且它们的名称将放置在__all__列表中。然后由您来验证/初始化/注册这些模块。自述文件中有一个示例。

I arrived here looking for a minimal plugin architecture, and found a lot of things that all seemed like overkill to me. So, I’ve implemented Super Simple Python Plugins. To use it, you create one or more directories and drop a special __init__.py file in each one. Importing those directories will cause all other Python files to be loaded as submodules, and their name(s) will be placed in the __all__ list. Then it’s up to you to validate/initialize/register those modules. There’s an example in the README file.


回答 9

实际上,setuptools可与“插件目录”一起使用,如以下示例摘自项目文档:http : //peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

用法示例:

plugin_dirs = ['foo/plugins'] + sys.path
env = Environment(plugin_dirs)
distributions, errors = working_set.find_plugins(env)
map(working_set.add, distributions)  # add plugins+libs to sys.path
print("Couldn't load plugins due to: %s" % errors)

从长远来看,setuptools是一个更安全的选择,因为它可以加载插件而不会发生冲突或缺少需求。

另一个好处是,插件本身可以使用相同的机制进行扩展,而原始应用程序不必关心它。

Actually setuptools works with a “plugins directory”, as the following example taken from the project’s documentation: http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

Example usage:

plugin_dirs = ['foo/plugins'] + sys.path
env = Environment(plugin_dirs)
distributions, errors = working_set.find_plugins(env)
map(working_set.add, distributions)  # add plugins+libs to sys.path
print("Couldn't load plugins due to: %s" % errors)

In the long run, setuptools is a much safer choice since it can load plugins without conflicts or missing requirements.

Another benefit is that the plugins themselves can be extended using the same mechanism, without the original applications having to care about it.


回答 10

作为插件系统的另一种方法,您可以检查Extend Me project

例如,让我们定义简单的类及其扩展

# Define base class for extensions (mount point)
class MyCoolClass(Extensible):
    my_attr_1 = 25
    def my_method1(self, arg1):
        print('Hello, %s' % arg1)

# Define extension, which implements some aditional logic
# or modifies existing logic of base class (MyCoolClass)
# Also any extension class maby be placed in any module You like,
# It just needs to be imported at start of app
class MyCoolClassExtension1(MyCoolClass):
    def my_method1(self, arg1):
        super(MyCoolClassExtension1, self).my_method1(arg1.upper())

    def my_method2(self, arg1):
        print("Good by, %s" % arg1)

并尝试使用它:

>>> my_cool_obj = MyCoolClass()
>>> print(my_cool_obj.my_attr_1)
25
>>> my_cool_obj.my_method1('World')
Hello, WORLD
>>> my_cool_obj.my_method2('World')
Good by, World

并显示隐藏在幕后的内容:

>>> my_cool_obj.__class__.__bases__
[MyCoolClassExtension1, MyCoolClass]

extend_me库通过元类操纵类的创建过程,因此在上面的示例中,当MyCoolClass我们创建新实例时,由于Python的多重继承,我们得到了新类的实例,该实例是这两者的子类MyCoolClassExtension并且MyCoolClass具有两者的功能

为了更好地控制类的创建,此库中定义了一些元类:

  • ExtensibleType -通过子类实现简单的可扩展性

  • ExtensibleByHashType -与ExtensibleType相似,但具有构建类的专用版本的能力,从而允许对基类进行全局扩展和对类的专用版本进行扩展

这个库在OpenERP Proxy Project中使用,似乎工作得很好!

有关用法的真实示例,请查看OpenERP Proxy’field_datetime’扩展名

from ..orm.record import Record
import datetime

class RecordDateTime(Record):
    """ Provides auto conversion of datetime fields from
        string got from server to comparable datetime objects
    """

    def _get_field(self, ftype, name):
        res = super(RecordDateTime, self)._get_field(ftype, name)
        if res and ftype == 'date':
            return datetime.datetime.strptime(res, '%Y-%m-%d').date()
        elif res and ftype == 'datetime':
            return datetime.datetime.strptime(res, '%Y-%m-%d %H:%M:%S')
        return res

Record这是易燃物品。RecordDateTime是扩展名。

要启用扩展,只需导入包含扩展类的模块,以及(在上述情况下)导入的所有Record对象,在其后创建的所有对象将在基类中具有扩展类,从而具有其所有功能。

该库的主要优点是,操作可扩展对象的代码不需要了解扩展,扩展可以更改可扩展对象中的所有内容。

As one another approach to plugin system, You may check Extend Me project.

For example, let’s define simple class and its extension

# Define base class for extensions (mount point)
class MyCoolClass(Extensible):
    my_attr_1 = 25
    def my_method1(self, arg1):
        print('Hello, %s' % arg1)

# Define extension, which implements some aditional logic
# or modifies existing logic of base class (MyCoolClass)
# Also any extension class maby be placed in any module You like,
# It just needs to be imported at start of app
class MyCoolClassExtension1(MyCoolClass):
    def my_method1(self, arg1):
        super(MyCoolClassExtension1, self).my_method1(arg1.upper())

    def my_method2(self, arg1):
        print("Good by, %s" % arg1)

And try to use it:

>>> my_cool_obj = MyCoolClass()
>>> print(my_cool_obj.my_attr_1)
25
>>> my_cool_obj.my_method1('World')
Hello, WORLD
>>> my_cool_obj.my_method2('World')
Good by, World

And show what is hidden behind the scene:

>>> my_cool_obj.__class__.__bases__
[MyCoolClassExtension1, MyCoolClass]

extend_me library manipulates class creation process via metaclasses, thus in example above, when creating new instance of MyCoolClass we got instance of new class that is subclass of both MyCoolClassExtension and MyCoolClass having functionality of both of them, thanks to Python’s multiple inheritance

For better control over class creation there are few metaclasses defined in this lib:

  • ExtensibleType – allows simple extensibility by subclassing

  • ExtensibleByHashType – similar to ExtensibleType, but haveing ability to build specialized versions of class, allowing global extension of base class and extension of specialized versions of class

This lib is used in OpenERP Proxy Project, and seems to be working good enough!

For real example of usage, look in OpenERP Proxy ‘field_datetime’ extension:

from ..orm.record import Record
import datetime

class RecordDateTime(Record):
    """ Provides auto conversion of datetime fields from
        string got from server to comparable datetime objects
    """

    def _get_field(self, ftype, name):
        res = super(RecordDateTime, self)._get_field(ftype, name)
        if res and ftype == 'date':
            return datetime.datetime.strptime(res, '%Y-%m-%d').date()
        elif res and ftype == 'datetime':
            return datetime.datetime.strptime(res, '%Y-%m-%d %H:%M:%S')
        return res

Record here is extesible object. RecordDateTime is extension.

To enable extension, just import module that contains extension class, and (in case above) all Record objects created after it will have extension class in base classes, thus having all its functionality.

The main advantage of this library is that, code that operates extensible objects, does not need to know about extension and extensions could change everything in extensible objects.


回答 11

setuptools具有一个EntryPoint

入口点是发行版“公告” Python对象(例如函数或类)供其他发行版使用的简单方法。可扩展的应用程序和框架可以从特定发行版或sys.path上的所有活动发行版中搜索具有特定名称或组的入口点,然后随意检查或加载广告对象。

如果您使用pip或virtualenv,此软件包始终可用。

setuptools has an EntryPoint:

Entry points are a simple way for distributions to “advertise” Python objects (such as functions or classes) for use by other distributions. Extensible applications and frameworks can search for entry points with a particular name or group, either from a specific distribution or from all active distributions on sys.path, and then inspect or load the advertised objects at will.

AFAIK this package is always available if you use pip or virtualenv.


回答 12

扩展@edomaur的答案,我建议您看一下simple_plugins(无耻插件),这是一个受Marty Alchin启发的简单插件框架。

一个基于项目自述文件的简短用法示例:

# All plugin info
>>> BaseHttpResponse.plugins.keys()
['valid_ids', 'instances_sorted_by_id', 'id_to_class', 'instances',
 'classes', 'class_to_id', 'id_to_instance']

# Plugin info can be accessed using either dict...
>>> BaseHttpResponse.plugins['valid_ids']
set([304, 400, 404, 200, 301])

# ... or object notation
>>> BaseHttpResponse.plugins.valid_ids
set([304, 400, 404, 200, 301])

>>> BaseHttpResponse.plugins.classes
set([<class '__main__.NotFound'>, <class '__main__.OK'>,
     <class '__main__.NotModified'>, <class '__main__.BadRequest'>,
     <class '__main__.MovedPermanently'>])

>>> BaseHttpResponse.plugins.id_to_class[200]
<class '__main__.OK'>

>>> BaseHttpResponse.plugins.id_to_instance[200]
<OK: 200>

>>> BaseHttpResponse.plugins.instances_sorted_by_id
[<OK: 200>, <MovedPermanently: 301>, <NotModified: 304>, <BadRequest: 400>, <NotFound: 404>]

# Coerce the passed value into the right instance
>>> BaseHttpResponse.coerce(200)
<OK: 200>

Expanding on the @edomaur’s answer may I suggest taking a look at simple_plugins (shameless plug), which is a simple plugin framework inspired by the work of Marty Alchin.

A short usage example based on the project’s README:

# All plugin info
>>> BaseHttpResponse.plugins.keys()
['valid_ids', 'instances_sorted_by_id', 'id_to_class', 'instances',
 'classes', 'class_to_id', 'id_to_instance']

# Plugin info can be accessed using either dict...
>>> BaseHttpResponse.plugins['valid_ids']
set([304, 400, 404, 200, 301])

# ... or object notation
>>> BaseHttpResponse.plugins.valid_ids
set([304, 400, 404, 200, 301])

>>> BaseHttpResponse.plugins.classes
set([<class '__main__.NotFound'>, <class '__main__.OK'>,
     <class '__main__.NotModified'>, <class '__main__.BadRequest'>,
     <class '__main__.MovedPermanently'>])

>>> BaseHttpResponse.plugins.id_to_class[200]
<class '__main__.OK'>

>>> BaseHttpResponse.plugins.id_to_instance[200]
<OK: 200>

>>> BaseHttpResponse.plugins.instances_sorted_by_id
[<OK: 200>, <MovedPermanently: 301>, <NotModified: 304>, <BadRequest: 400>, <NotFound: 404>]

# Coerce the passed value into the right instance
>>> BaseHttpResponse.coerce(200)
<OK: 200>

回答 13

当我不时地在Python中搜索插件框架时,我花了一些时间阅读该线程。我用了一些,但是它们有缺点。这是我在2017年对您进行详细审查时想出的结果,这是一个无接口,松耦合的插件管理系统:稍后再加载。这是有关如何使用它的教程

I have spent time reading this thread while I was searching for a plugin framework in Python now and then. I have used some but there were shortcomings with them. Here is what I come up with for your scrutiny in 2017, a interface free, loosely coupled plugin management system: Load me later. Here are tutorials on how to use it.


回答 14

您可以使用pluginlib

插件易于创建,可以从其他软件包,文件路径或入口点加载。

创建一个插件父类,定义任何必需的方法:

import pluginlib

@pluginlib.Parent('parser')
class Parser(object):

    @pluginlib.abstractmethod
    def parse(self, string):
        pass

通过继承父类来创建插件:

import json

class JSON(Parser):
    _alias_ = 'json'

    def parse(self, string):
        return json.loads(string)

加载插件:

loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))

You can use pluginlib.

Plugins are easy to create and can be loaded from other packages, file paths, or entry points.

Create a plugin parent class, defining any required methods:

import pluginlib

@pluginlib.Parent('parser')
class Parser(object):

    @pluginlib.abstractmethod
    def parse(self, string):
        pass

Create a plugin by inheriting a parent class:

import json

class JSON(Parser):
    _alias_ = 'json'

    def parse(self, string):
        return json.loads(string)

Load the plugins:

loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))

回答 15

我花了很多时间试图找到适合我的Python小型插件系统。但是然后我只是想,如果已经有了一个自然而灵活的继承,为什么不使用它。

对插件使用继承的唯一问题是您不知道最具体的(继承树上最低的)插件类是什么。

但这可以通过元类来解决,它可以跟踪基类的继承,并可以构建类,该类继承自大多数特定的插件(下图为“ Root extended”)

因此,我通过编码这样的元类提供了一个解决方案:

class PluginBaseMeta(type):
    def __new__(mcls, name, bases, namespace):
        cls = super(PluginBaseMeta, mcls).__new__(mcls, name, bases, namespace)
        if not hasattr(cls, '__pluginextensions__'):  # parent class
            cls.__pluginextensions__ = {cls}  # set reflects lowest plugins
            cls.__pluginroot__ = cls
            cls.__pluginiscachevalid__ = False
        else:  # subclass
            assert not set(namespace) & {'__pluginextensions__',
                                         '__pluginroot__'}     # only in parent
            exts = cls.__pluginextensions__
            exts.difference_update(set(bases))  # remove parents
            exts.add(cls)  # and add current
            cls.__pluginroot__.__pluginiscachevalid__ = False
        return cls

    @property
    def PluginExtended(cls):
        # After PluginExtended creation we'll have only 1 item in set
        # so this is used for caching, mainly not to create same PluginExtended
        if cls.__pluginroot__.__pluginiscachevalid__:
            return next(iter(cls.__pluginextensions__))  # only 1 item in set
        else:
            name = cls.__pluginroot__.__name__ + 'PluginExtended'
            extended = type(name, tuple(cls.__pluginextensions__), {})
            cls.__pluginroot__.__pluginiscachevalid__ = True
return extended

因此,当您具有由元类构成的Root基础并且具有从其继承的插件树时,您可以自动获取类,该类可以通过子类化而从最特定的插件继承:

class RootExtended(RootBase.PluginExtended):
    ... your code here ...

代码库很小(约30行纯代码),并且在继承允许的范围内具有足够的灵活性。

如果您有兴趣,请参与@ https://github.com/thodnev/pluginlib

I’ve spent a lot of time trying to find small plugin system for Python, which would fit my needs. But then I just thought, if there is already an inheritance, which is natural and flexible, why not use it.

The only problem with using inheritance for plugins is that you dont know what are the most specific(the lowest on inheritance tree) plugin classes are.

But this could be solved with metaclass, which keeps track of inheritance of base class, and possibly could build class, which inherits from most specific plugins (‘Root extended’ on the figure below)

So I came with a solution by coding such a metaclass:

class PluginBaseMeta(type):
    def __new__(mcls, name, bases, namespace):
        cls = super(PluginBaseMeta, mcls).__new__(mcls, name, bases, namespace)
        if not hasattr(cls, '__pluginextensions__'):  # parent class
            cls.__pluginextensions__ = {cls}  # set reflects lowest plugins
            cls.__pluginroot__ = cls
            cls.__pluginiscachevalid__ = False
        else:  # subclass
            assert not set(namespace) & {'__pluginextensions__',
                                         '__pluginroot__'}     # only in parent
            exts = cls.__pluginextensions__
            exts.difference_update(set(bases))  # remove parents
            exts.add(cls)  # and add current
            cls.__pluginroot__.__pluginiscachevalid__ = False
        return cls

    @property
    def PluginExtended(cls):
        # After PluginExtended creation we'll have only 1 item in set
        # so this is used for caching, mainly not to create same PluginExtended
        if cls.__pluginroot__.__pluginiscachevalid__:
            return next(iter(cls.__pluginextensions__))  # only 1 item in set
        else:
            name = cls.__pluginroot__.__name__ + 'PluginExtended'
            extended = type(name, tuple(cls.__pluginextensions__), {})
            cls.__pluginroot__.__pluginiscachevalid__ = True
return extended

So when you have Root base, made with metaclass, and have tree of plugins which inherit from it, you could automatically get class, which inherits from the most specific plugins by just subclassing:

class RootExtended(RootBase.PluginExtended):
    ... your code here ...

Code base is pretty small (~30 lines of pure code) and as flexible as inheritance allows.

If you’re interested, get involved @ https://github.com/thodnev/pluginlib


回答 16

您也可以看看Groundwork

这个想法是围绕可重用​​的组件(称为模式和插件)构建应用程序。插件是从派生的类GwBasePattern。这是一个基本示例:

from groundwork import App
from groundwork.patterns import GwBasePattern

class MyPlugin(GwBasePattern):
    def __init__(self, app, **kwargs):
        self.name = "My Plugin"
        super().__init__(app, **kwargs)

    def activate(self): 
        pass

    def deactivate(self):
        pass

my_app = App(plugins=[MyPlugin])       # register plugin
my_app.plugins.activate(["My Plugin"]) # activate it

还有更高级的模式可以处理例如命令行界面,信令或共享对象。

Groundwork可以通过以编程方式将其绑定到应用程序(如上所示)或通过来自动找到其插件setuptools。包含插件的Python包必须使用特殊的入口点来声明它们groundwork.plugin

这是文档

免责声明:我是Groundwork的作者之一。

You may also have a look at Groundwork.

The idea is to build applications around reusable components, called patterns and plugins. Plugins are classes that derive from GwBasePattern. Here’s a basic example:

from groundwork import App
from groundwork.patterns import GwBasePattern

class MyPlugin(GwBasePattern):
    def __init__(self, app, **kwargs):
        self.name = "My Plugin"
        super().__init__(app, **kwargs)

    def activate(self): 
        pass

    def deactivate(self):
        pass

my_app = App(plugins=[MyPlugin])       # register plugin
my_app.plugins.activate(["My Plugin"]) # activate it

There are also more advanced patterns to handle e.g. command line interfaces, signaling or shared objects.

Groundwork finds its plugins either by programmatically binding them to an app as shown above or automatically via setuptools. Python packages containing plugins must declare these using a special entry point groundwork.plugin.

Here are the docs.

Disclaimer: I’m one of the authors of Groundwork.


回答 17

在当前的医疗保健产品中,我们具有使用接口类实现的插件体系结构。我们的技术堆栈是在Python之上的Django(用于API)和Nuxtjs(在nodejs的在前端)。

我们为我们的产品编写了一个插件管理器应用程序,该应用程序基本上是pip和npm软件包,遵循Django和Nuxtjs。

对于新的插件开发(pip和npm),我们将插件管理器作为依赖项。

在Pip包中:在setup.py的帮助下,您可以添加插件的入口点,以使用插件管理器(注册表,启动等)进行某些操作 https://setuptools.readthedocs.io/zh/latest/setuptools .html#automatic-script-creation

在npm软件包中:与pip相似,npm脚本中有钩子可以处理安装。 https://docs.npmjs.com/misc/scripts

我们的用例:

插件开发团队现已与核心开发团队分离。插件开发的范围是用于与在产品的任何类别中定义的第三方应用程序集成。插件界面的分类例如:-传真,电话,电子邮件…等插件管理器可以增强到新的类别。

在您的情况下:也许您可以编写一个插件,然后将其重复用于做事。

如果插件开发人员需要使用重用核心对象,则可以通过在插件管理器中进行一定程度的抽象来使用该对象,以便任何插件都可以继承这些方法。

只是分享我们在产品中的实现方式,希望对您有所帮助。

In our current healthcare product we have a plugin architecture implemented with interface class. Our tech stack are Django on top of Python for API and Nuxtjs on top of nodejs for frontend.

We have a plugin manager app written for our product which is basically pip and npm package in adherence with Django and Nuxtjs.

For new plugin development(pip and npm) we made plugin manager as dependency.

In Pip package: With the help of setup.py you can add entrypoint of the plugin to do something with plugin manager(registry, initiations, …etc.) https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation

In npm package: Similar to pip there are hooks in npm scripts to handle the installation. https://docs.npmjs.com/misc/scripts

Our usecase:

plugin development team is separate from core devopment team now. The scope of plugin development is for integrating with 3rd party apps which are defined in any of the categories of the product. The plugin interfaces are categorised for eg:- Fax, phone, email …etc plugin manager can be enhanced to new categories.

In your case: Maybe you can have one plugin written and reuse the same for doing stuffs.

If plugin developers has need to use reuse core objects that object can be used by doing a level of abstraction within plugin manager so that any plugins can inherit those methods.

Just sharing how we implemented in our product hope it will give a little idea.


为什么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.