Python元类和导入*

Python元类和导入*,python,Python,主要目标:在工厂中自动注册类(通过字符串),以便在运行时使用该字符串动态创建,类可以在它们自己的文件中,而不是在一个文件中分组 我有两个类,它们都继承自同一个基类,它们定义了一个字符串作为它们的类型 用户希望获取其中一个类的实例,但在运行时只知道其类型 因此,我有一个工厂来创建给定类型的实例。 我不想硬编码“if-then语句”,所以我有一个元类来注册基类的所有子类: class MetaRegister(type): # we use __init__ rather than __ne

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

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

用户希望获取其中一个类的实例,但在运行时只知道其类型

因此,我有一个工厂来创建给定类型的实例。 我不想硬编码“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)
问题是工厂必须导入所有的子类(这样元类才能运行)。 要解决此问题,您可以使用X导入中的
*
但要使其正常工作,您需要在包的
\uuuuu init\uuuuuuuuuy.py
文件中定义一个
\uuuuuuu all\uuuuuuu
变量,以包含所有子类

我不想硬编码子类,因为它超出了使用元类的目的

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

import glob

from os.path import dirname, basename, isfile

modules = glob.glob(dirname(__file__) + "/*.py")
__all__ = [basename(f)[:-3] for f in modules if isfile(f)]
这非常有效,但是项目需要编译成一个.so文件,这使得文件系统的使用无效

那么,在不硬编码类型的情况下,如何实现在运行时创建实例的主要目标呢

有没有一种方法可以在运行时填充
\uuuuuuuuuuuuuuuuuuuuuuuuuu
变量而不触及文件系统

在Java中,我可能会用注释装饰类,然后在运行时用注释获得所有类,python上有类似的东西吗

我知道python中有装饰器,但我不确定是否可以这样使用它们

编辑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:

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方法将起作用

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

.
├── __init__.py
├── plugins
│   ├── alpha.py
│   └── beta.py
└── register.py
“插件”包含在
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
目前,导入上面的包不会导致注册任何类。这是因为类定义永远不会运行,因为包含它们的模块永远不会导入。补救方法是为
插件
文件夹自动生成一个
\uuu init\uuuuuuuuuuuupy
。下面是一个脚本,它正是这样做的——这个脚本可以成为编译过程的一部分

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'>}
导入mypkg >>>mypkg.register.registry {'Beta':, 'Alpha':}
请注意,同样的方法也适用于使用元类注册插件类,我只是更喜欢在这里使用decorators。

将类注册为运行时的简单方法是使用decorators:

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方法将起作用

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

.
├── __init__.py
├── plugins
│   ├── alpha.py
│   └── beta.py
└── register.py
“插件”包含在
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
目前,导入上面的包不会导致注册任何类。这是因为类定义永远不会运行,因为包含它们的模块永远不会导入。补救方法是为
插件
文件夹自动生成一个
\uuu init\uuuuuuuuuuuupy
。下面是一个脚本,它正是这样做的——这个脚本可以成为编译过程的一部分

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'>}
导入mypkg >>>mypkg.register.registry {'Beta':, 'Alpha':} 注tha