我正在寻找允许用户覆盖应用程序中的模块或使用新模块扩展应用程序的技术。
想象一下名为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完成此任务?
答案 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__.py
和pydraw/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.py
或square.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)
您可以使用特定于操作系统的方法检测特定目录中的文件更改。对于所有操作系统,都存在文件监视工具,可在更改文件或目录时生成事件。或者,您可以连续搜索比上次搜索时间更新的文件。有多种可能的解决方案,但无论如何:
__import__
内置函数reload
内置函数重新导入编辑:
如果将插件目录添加为PYTHONPATH中的第一个目录,则该目录将优先于pythonpath中的其他目录,例如。
import sys
sys.path.insert(0, 'PLUGIN_DIR')