使用元类进行自注册插件? (接近帮助)

Posted

技术标签:

【中文标题】使用元类进行自注册插件? (接近帮助)【英文标题】:Using Metaclasses for self-registering plugins? (Approach help) 【发布时间】:2018-02-13 15:18:10 【问题描述】:

我正在尝试编写一个在运行时加载某些类的 Python (2.7) 库。这些类包含一组预定义的方法。

我的方法是定义一些在我的库中使用的元类。例如,我会定义一个“导航”元类并在库中使用它。然后有人可以编写一个类“Mainmenu”,其中包含某种类型的定义,它是一个“导航”插件。然后图书馆就可以使用这个类了。

我能够加载模块并且能够编写元类。我的问题在于将这两件事结合起来。

首先存在的问题是我希望“插件类”位于不同的(可配置的)文件夹中。所以我不能这样做:

__metaclass__ = Navigation

因为 Navigation 类是我的库的一部分,并且不会出现在插件文件夹中... 我怎样才能解决告诉插件的类型的问题? (导航、内容......例如)

EDIT2:我解决了以下问题。我发现我可以要求模块给我一个字典。 我的第一个问题仍然存在

编辑:

我管理注册并使用注册表加载“普通”类,直到以下点:

from os import listdir
from os.path import isfile, join
import imp


class State:
    registry = 

    def register_class(self,target_class):
        self.registry[target_class.__name__] = target_class
        print target_class.__name__+" registered!"

    def create(self,classname):
        tcls = self.registry[classname]
        print self.registry[classname]
        return tcls()

s = State()
mypath = """C:\metatest\plugins"""
files = [f for f in listdir(mypath) if isfile(join(mypath, f))]
for f in files:
    spl = f.split(".")
    if spl[1] == "py":
        a = imp.load_source(spl[0], mypath + """\\""" + f)
        s.register_class(a)

我现在最后的问题是,“a”是加载的模块,所以它是一个模块对象。就我而言,它只有一个类。 如何从加载的模块中获取 Class 对象,以便正确注册该类??

【问题讨论】:

首先要注意的是:如果这是 Python 2,你的类必须object 继承(拥有适当的元类集将为你做到这一点)。否则,它是一个“旧式”类,许多理所当然的机制根本不适用于您的对象。第二件事:你没有在新代码中使用 Python 3,因为? 【参考方案1】:

所以 - 让我们检查一下您对当前提案的问题。 您需要一种方法来为更大的系统提供插件 - 更大的系统在编码时不会知道插件 - 但 converse 不正确:您的插件应该能够在更大的系统上加载模块、导入基类和调用函数。

除非您真的拥有如此可插入的东西,以至于插件可以与多个更大的系统一起使用。我对此表示怀疑,但如果是这种情况,您需要一个可以注册接口并检索不同类之间的类和适配器实现的框架。该框架是 Zope Interface - 您应该在此处阅读有关它的文档:https://zopeinterface.readthedocs.io/en/latest/

更实际的是一个插件系统,它可以为 Python 文件搜索一些预设目录并导入这些目录。正如我上面所说,如果这些文件确实在您的主系统上导入基类(或元类,以供记录)没有问题:无论如何,这些文件已经由 Python 在运行过程中导入,它们在插件中的导入将只是然后在插件代码中显示为可用。

您可以使用上面的确切代码,只需添加一个简短的元类来从 State 注册派生类 - 您可以约定不同插件类别的每个基类都具有 registry 属性:

class RegistryMeta(type):
    def __init__(cls, name, bases, namespace):
        for base in cls.__mro__:
            if 'registry' in base.__dict__:
                  if cls.__name__ in base.registry:
                       raise ValueError("Attempting to registrate plug-in with the name  which is already taken".format(cls.__name__))
                  base.registry[cls.__name__] = cls
                  break
        super(RegistryMeta, cls).__init__(name, base, namespace)

class State(object):
    __metaclass__ = RegistryMeta
    registry = 
    ...

(保留扫描目录和加载模块的代码 - 只需将所有目录分隔栏切换到“/” - 你仍然没有做对,使用“\”会感到意外)

关于插件代码包括:

from mysystem import State

class YourClassCode(State):
   ...

最后,正如我在评论中所说:您应该真的检查使用 Python 3.6 的可能性。除了其他细节之外,您还可以使用 __init_subclass__ 特殊方法,而无需使用自定义元类来保存您的注册表。

【讨论】:

谢谢!我仍然必须完全理解所有这些,但这似乎正是我想要的!我会使用 Python 3.6,但我不能......现有的结构和老板和东西:D 好的。现在详细查看了这个,有一些我不相信的东西: 1.我将状态对象视为保存和维护已加载插件注册表的对象。为什么我希望插件从 State 继承? 2. 我想我不完全明白这里发生了什么。有没有比 "and cls.__name__ != "Plugin"" 更优雅的方式来让 State 本身脱离注册表 3. (*args, **kwargs) 来自哪里? 4.我真的不明白这句话“你可以约定不同插件类别的每个基类都有注册表属性” 啊,我想我现在明白了。你的意思是我可以为不同的插件类别提供不同版本的 State 类,对吧?? 是的。 - 不同插件类别的不同类别。 args 和 kwargs 的存在只是因为我之前的想法中的复制和粘贴错误 - 我现在已经修复了它们。

以上是关于使用元类进行自注册插件? (接近帮助)的主要内容,如果未能解决你的问题,请参考以下文章

使用元类动态设置属性

元类(metaclass)

具有自定义元类行为的 Python 元类

使用 Boost.Python 设置包装类的元类

学习日记0827异常处理 元类 自定义元类 自定义元类来实例化类 属性查找顺序

为啥在元类派生自ABCMeta的类中需要使用@abstractmethod?