Python元类和导入*

2024-05-13 04:11:54 发布

您现在位置:Python中文网/ 问答频道 /正文

主要目标:自动注册工厂中的类(通过字符串),以便在运行时使用该字符串动态创建,类可以在自己的文件中,而不是分组在一个文件中。在

我有几个类都继承自同一个基类,它们定义了一个字符串作为它们的类型。在

用户希望获得这些类之一的实例,但在运行时只知道类型。在

因此,我有一个工厂来创建一个给定类型的实例。 我不想硬编码“if-then语句”,所以我有一个元类来注册基类的所有子类:

class MetaRegister(type):
    # we use __init__ rather than __new__ here because we want
    # to modify attributes of the class *after* they have been
    # created
    def __init__(cls, name, bases, dct):
        if not hasattr(cls, 'registry'):
            # this is the base class.  Create an empty registry
            cls.registry = {}
        else:
            # this is a derived class.  Add cls to the registry
            interface_id = cls().get_model_type()
            cls.registry[interface_id] = cls

        super(MetaRegister, cls).__init__(name, bases, dct)

问题是,要使其工作,工厂必须导入所有的子类(因此元类运行)。 要修复此问题,可以使用from X import * 但要实现这一点,您需要在包的__init__.py文件中定义一个__all__var来包含所有的子类。在

我不想对子类进行硬编码,因为它违背了使用元类的目的。在

我可以使用以下方法查看包中的文件:

^{pr2}$

这很好,但是项目需要编译成一个.so文件,这就取消了文件系统的使用。在

那么,如何才能实现在运行时创建实例而不必对类型进行硬编码的主要目标呢?在

有没有一种方法可以在运行时填充一个__all__var而不接触文件系统?在

在Java中,我可能会用一个注释来修饰类,然后在运行时得到所有带有该注释的类,python上有类似的东西吗?在

我知道python中有decorator,但我不确定是否可以用这种方式使用它们。在

编辑1: 每个子类必须在一个文件中:

- Models
-- __init__.py
-- ModelFactory.py
-- Regression
--- __init__.py
--- Base.py
--- Subclass1.py
--- Subclass2ExtendsSubclass1.py

编辑2:一些代码来说明问题:

+ main.py
|__ Models
    |__ __init__.py
    |__ ModelFactory.py
    |__ Regression
        |__ init__.py
        |__ Base.py
        |__ SubClass.py
        |__ ModelRegister.py

main.py

from models.ModelFactory import ModelFactory

if __name__ == '__main__':
    ModelFactory()


ModelFactory.py

from models.regression.Base import registry
import models.regression

class ModelFactory(object):
    def get(self, some_type):
        return registry[some_type]


ModelRegister.py
class ModelRegister(type):
    # we use __init__ rather than __new__ here because we want
    # to modify attributes of the class *after* they have been
    # created
    def __init__(cls, name, bases, dct):
        print cls.__name__
        if not hasattr(cls, 'registry'):
            # this is the base class.  Create an empty registry
            cls.registry = {}
        else:
            # this is a derived class.  Add cls to the registry
            interface_id = cls().get_model_type()
            cls.registry[interface_id] = cls

        super(ModelRegister, cls).__init__(name, bases, dct)

Base.py

from models.regression.ModelRegister import ModelRegister

class Base(object):
    __metaclass__ = ModelRegister

    def get_type(self):
        return "BASE"

SubClass.py

from models.regression.Base import Base


class SubClass(Base):
    def get_type(self):
        return "SUB_CLASS"

运行它你只能看到“基地”它打印。 使用decorator可以得到相同的结果。在


Tags: 文件thenamepyimportbasegetinit
3条回答

我会用动态导入来做这个。在

模型/回归/基准.py

class Base(object):
    def get_type(self):
        return "BASE"

模型/回归/子类.py

^{pr2}$

加载程序.py

from importlib import import_module

class_name = "subclass"
module = import_module("models.regression.%s" % class_name)
model = module.__myclass__()
print(model.get_type())

并清空models/models/regression/中的__init__.py文件

有:

nuitka  recurse-none  recurse-directory models  module loader.py

结果装载机.so包含models/子目录下的所有模块。在

将类注册为运行时的一种简单方法是使用修饰符:

registry = {}

def register(cls):
    registry[cls.__name__] = cls
    return cls

@register
class Foo(object):
    pass

@register
class Bar(object):
    pass

如果所有的类都定义在同一个模块中,并且该模块是在运行时导入的,那么这将起作用。然而,你的处境使事情复杂化。首先,您需要在不同的模块中定义类。这意味着我们必须能够在运行时动态确定包中存在哪些模块。使用Python的pkgutil模块可以很容易地实现这一点,但是,您还声明您正在使用Nuitka将您的包编译成扩展模块。pkgutil不能与这样的扩展模块一起工作。在

我找不到任何文档化的方法来从Python中确定Nuitka扩展模块中包含的模块。如果确实存在,那么上面的decorator方法将在动态导入每个子模块之后工作。在

事实上,我认为最直接的解决方案是在编译之前编写一个脚本来生成__init__.py。假设我们有以下包结构:

^{pr2}$

“插件”包含在plugins目录中。文件内容包括:

# register.py
#      -

registry = {}
def register(cls):
    registry[cls.__name__] = cls
    return cls

# __init__.py
#      -

from . import plugins
from . import register


# ./plugins/alpha.py
#          

from ..register import register

@register
class Alpha(object):
    pass


# ./plugins/beta.py
#          

from ..register import register

@register
class Beta(object):
    pass

目前,导入上面的包不会导致注册任何类。这是因为类定义从不运行,因为包含它们的模块从不被导入。补救方法是为plugins文件夹自动生成__init__.py。下面是一个脚本,这个脚本可以作为编译过程的一部分。在

import pathlib


root = pathlib.Path('./mypkg/plugins')
exclude = {'__init__.py'}

def gen_modules(root):
    for entry in root.iterdir():
        if entry.suffix == '.py' and entry.name not in exclude:
            yield entry.stem

with (root / '__init__.py').open('w') as fh:
    for module in gen_modules(root):
        fh.write('from . import %s\n' % module)

将此脚本放在包根目录上方一个目录(假设您的包名为mypkg),然后运行它会得到:

from . import alpha
from . import beta

现在测试:我们编译包:

nuitka  module mypkg  recurse-to=mypkg

然后尝试导入它,检查所有类是否正确注册:

>>> import mypkg
>>> mypkg.register.registry
{'Beta': <class 'mypkg.plugins.beta.Beta'>, 
 'Alpha': <class 'mypkg.plugins.alpha.Alpha'>}

注意,同样的方法也可以使用元类来注册插件类,我只是更喜欢在这里使用decorator。在

如果反射的类使用您的元类,则不需要使用from X import *来注册它们。只有import X就足够了。一旦导入包含类的模块,这些类将被创建并在元类注册表中可用。在

相关问题 更多 >