在Python中启用用户代码扩展的流行技术是什么?

时间:2011-08-26 07:33:02

标签: oop python-3.x python

我正在寻找允许用户覆盖应用程序中的模块或使用新模块扩展应用程序的技术。

想象一下名为pydraw的应用程序。它目前提供了一个Circle类,它继承了Shape。包树可能如下所示:

/usr/lib/python/
└── pydraw
    ├── __init__.py
    ├── shape.py
    └── shapes
        ├── circle.py
        └── __init__.py

现在假设我想启用动态发现和加载实现新形状的用户模块,或者甚至是Shape类本身。对于用户的树来说,与应用程序树具有相同的结构似乎是最直接的,例如:

/home/someuser/python/
└── pydraw
    ├── __init__.py
    ├── shape.py       <-- new superclass
    └── shapes
        ├── __init__.py
        └── square.py   <-- new user class

换句话说,我想用来自用户树的同名文件覆盖和屏蔽应用程序树,或者至少从Python的角度来看这个明显的结构。

然后,通过配置sys.path或PYTHONPATH,pydraw.shapes.square可能是可发现的。但是,Python的模块路径搜索找不到像square.py这样的模块。我认为这是因为__method__已在另一条路径中包含父模块。

您将如何使用Python完成此任务?

4 个答案:

答案 0 :(得分:3)

扩展的发现可能有点脆弱和复杂,并且还要求您查看可能非常大的所有PYTHONPATH。

相反,请使用配置文件列出应加载的插件。这可以通过将它们列为模块名称,并且还要求它们位于PYTHONPATH,或者只是列出完整路径来完成。

如果你想要每个用户级配置,我既有一个列出模块的全局配置文件,又有一个每个用户一个,只读这些配置文件而不是尝试一些发现机制。

此外,您不仅要尝试添加插件,还要覆盖应用程序的组件。为此,我将使用Zope组件架构。然而,它还没有完全移植到Python 3,但它被设计用于这种情况,尽管从你的描述中看起来这似乎是一个简单的情况,而ZCA可能有点过分。但无论如何要看它。

答案 1 :(得分:1)

我用来处理这个问题的方法是使用provider pattern

在你的模块shape.py中:

class BaseShape:
   def __init__(self):
      pass
provider("BaseShape", BaseShape)

并在用户的shape.py模块中:

class UserBaseShape:
   def __init__(self):
      pass
provider("BaseShape", UserBaseShape)

使用provider方法执行以下操作:

def provider(provide_key, provider_class):
   global_providers[provide_key] = provider_class

当您需要实例化对象时,请使用像这样的ProvideFactory

class ProvideFactory:
   def get(self, provide_key, *args, **kwargs):
      return global_providers[provide_key](*args, **kwargs)

答案 2 :(得分:1)

如果要从不同位置动态加载python代码,可以使用pkgutil module扩展搜索__path__属性:

将这些行放入每个pydraw/__init__.pypydraw/shapes/__init__.py

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

您将能够编写import语句,就好像您有一个独特的包:

>>> import pydraw.shapes
>>> pydraw.shapes.__path__
['/usr/lib/python/pydraw/shapes', '/home/someuser/python/pydraw/shapes']
>>> from pydraw.shapes import circle, square
>>>

您可以考虑自动注册插件。你仍然可以通过设置一个模块变量(它将作为一种单例模式)来使用基本的python代码。

在每个pydraw/shapes/__init__.py文件中添加最后一行:

 from pkgutil import extend_path
 __path__ = extend_path(__path__, __name__)

 # your shape registry
 __shapes__ = []

您现在可以在相关模块的顶部注册一个形状(circle.pysquare.py此处)。

 from pydraw.shapes import __shapes__
 __shapes__.append(__name__)

上次检查:

 >>> from pydraw.shapes import circle,square
 >>> from pydraw.shapes import circle,square,__shapes__
 >>> __shapes__
 ['pydraw.shapes.circle', 'pydraw.shapes.square']

答案 3 :(得分:0)

您可以使用特定于操作系统的方法检测特定目录中的文件更改。对于所有操作系统,都存在文件监视工具,可在更改文件或目录时生成事件。或者,您可以连续搜索比上次搜索时间更新的文件。有多种可能的解决方案,但无论如何:

  • 配置插件目录比监视整个文件系统容易得多。
  • 在单独的线程中查找文件更改可能是最佳解决方案
  • 如果找到 new .py文件,您可以使用__import__内置函数
  • 导入它
  • 如果 已更改,您可以使用reload内置函数重新导入
  • 如果更改了类,该类的实例仍将像旧类的实例一样,因此请确保在必要时重新创建实例

编辑:

如果将插件目录添加为PYTHONPATH中的第一个目录,则该目录将优先于pythonpath中的其他目录,例如。

import sys
sys.path.insert(0, 'PLUGIN_DIR')