Oop Python中支持用户代码扩展的常用技术有哪些?
我正在寻找允许用户覆盖应用程序中的模块或使用新模块扩展应用程序的技术 想象一个名为pydraw的应用程序。它当前提供了一个圆形类,该类继承了形状。包树可能如下所示:Oop Python中支持用户代码扩展的常用技术有哪些?,oop,python-3.x,python,Oop,Python 3.x,Python,我正在寻找允许用户覆盖应用程序中的模块或使用新模块扩展应用程序的技术 想象一个名为pydraw的应用程序。它当前提供了一个圆形类,该类继承了形状。包树可能如下所示: /usr/lib/python/ └── pydraw ├── __init__.py ├── shape.py └── shapes ├── circle.py └── __init__.py 现在假设我想启用动态发现和加载实现新形状的用户模块,或者甚至是形状类本身。用户的
/usr/lib/python/
└── pydraw
├── __init__.py
├── shape.py
└── shapes
├── circle.py
└── __init__.py
现在假设我想启用动态发现和加载实现新形状的用户模块,或者甚至是形状类本身。用户的树与应用程序树具有相同的结构似乎是最简单的,例如:
/home/someuser/python/
└── pydraw
├── __init__.py
├── shape.py <-- new superclass
└── shapes
├── __init__.py
└── square.py <-- new user class
/home/someuser/python/
└── 皮德鲁
├── __初始值
├── shape.py发现扩展可能有点脆弱和复杂,而且还需要查看所有可能非常大的PYTHONPATH
相反,要有一个配置文件,列出应该加载的插件。这可以通过将它们列为模块名来实现,还需要它们位于PYTHONPATH上,或者只列出完整路径
如果您想要每个用户级别的配置,我会有一个列出模块的全局配置文件和一个每个用户的配置文件,并且只需读取这些配置文件,而不是尝试一些发现机制
此外,您还试图不仅添加插件,而且覆盖应用程序的组件。为此,我将使用Zope组件体系结构。然而,它还没有完全移植到Python3,但是它被设计用于这种情况,尽管从您的描述来看,这似乎是一个简单的情况,ZCA可能有点过头了。但无论如何,看看它
我用来处理这样一个问题的方法是使用一个
在模块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
当您需要实例化一个对象时,请使用如下ProviderFactory
class ProvideFactory:
def get(self, provide_key, *args, **kwargs):
return global_providers[provide_key](*args, **kwargs)
您可以使用特定于操作系统的方法检测特定目录中的文件更改。对于所有操作系统,都存在文件监视工具,当文件或目录发生更改时,这些工具会生成事件。或者,您可以连续搜索比上次搜索时间新的文件。有多种可能的解决方案,但在任何情况下:
- 配置插件目录比监视整个文件系统容易得多
- 在单独的线程中查找文件更改可能是最好的解决方案
- 如果找到一个新的.py文件,您可以使用
\uuuuu import\uuuu
内置函数导入它
- 如果.py被更改,您可以使用
reload
内置函数重新导入它
- 如果更改了一个类,该类的实例仍将像旧类的实例一样运行,因此请确保在必要时重新创建实例
编辑:
如果将插件目录添加为PYTHONPATH中的第一个目录,则该目录将优先于PYTHONPATH中的其他目录,例如
import sys
sys.path.insert(0, 'PLUGIN_DIR')
如果要从不同位置动态加载python代码,可以使用以下命令扩展搜索路径属性:
通过将这些行放入每个pydraw/\uuuuu init\uuuuu.py
和pydraw/shapes/\uuuuu init\uuuuu.py
:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
您将能够编写导入语句,就像您有一个唯一的包:
>>> 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/\uuuu init\uuuuu.py
文件中添加最后一行:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
# your shape registry
__shapes__ = []
现在,您可以在其相关模块的顶部注册一个形状(circle.py
或square.py
)
最后检查:
>>> from pydraw.shapes import circle,square
>>> from pydraw.shapes import circle,square,__shapes__
>>> __shapes__
['pydraw.shapes.circle', 'pydraw.shapes.square']
你可以通过使用装饰器来清理一下。我真的不明白“提供者模式”这个术语从何而来;在我看来,这就像一个相当普通的抽象工厂。@Karl Knechtel:是的,我们可以使用类装饰器,但在本例中,我试图解释这个想法,而不是真正实现所有;)。对于provider模式,你是对的,它非常接近抽象工厂模式,我曾将其命名为provider模式,因为这是我被教导的方式……这与ZCA的做法类似,但当然功能要少得多。在不需要外部依赖的简单情况下,这可能是有意义的。谢谢Julien-extend_path正是我想要的!我错误地认为一个包只能从一个目录树加载。
>>> from pydraw.shapes import circle,square
>>> from pydraw.shapes import circle,square,__shapes__
>>> __shapes__
['pydraw.shapes.circle', 'pydraw.shapes.square']