首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python metaClass和导入*

Python metaClass和导入*
EN

Stack Overflow用户
提问于 2015-12-08 16:05:43
回答 3查看 2K关注 0票数 3

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

我有两个类,它们都是从同一个基类继承的,它们将字符串定义为它们的类型。

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

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

代码语言:javascript
复制
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__变量,以包含所有的子类。

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

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

代码语言:javascript
复制
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文件,这将取消对文件系统的使用。

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

是否有一种方法可以在运行时填充__all__变量而不接触文件系统?

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

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

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

代码语言:javascript
复制
- Models
-- __init__.py
-- ModelFactory.py
-- Regression
--- __init__.py
--- Base.py
--- Subclass1.py
--- Subclass2ExtendsSubclass1.py

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

代码语言:javascript
复制
+ 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"

运行它时,您只能看到打印的“基本”。使用装饰器可以得到同样的结果。

EN

回答 3

Stack Overflow用户

发布于 2015-12-08 16:20:33

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

代码语言:javascript
复制
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扩展模块中包含的模块的任何有文档的方法。如果确实存在,上面的装饰方法在动态导入每个子模块之后就可以工作了。

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

代码语言:javascript
复制
.
├── __init__.py
├── plugins
│   ├── alpha.py
│   └── beta.py
└── register.py

“插件”包含在plugins目录中。这些文件的内容如下:

代码语言:javascript
复制
# 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文件夹生成一个plugins。下面是一个脚本,它正是这样做的--这个脚本可以成为编译过程的一部分。

代码语言:javascript
复制
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)并运行它:

代码语言:javascript
复制
from . import alpha
from . import beta

现在是测试:我们编译了包:

代码语言:javascript
复制
nuitka --module mypkg --recurse-to=mypkg

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

代码语言:javascript
复制
>>> import mypkg
>>> mypkg.register.registry
{'Beta': <class 'mypkg.plugins.beta.Beta'>, 
 'Alpha': <class 'mypkg.plugins.alpha.Alpha'>}

注意,同样的方法也适用于使用元类注册插件类,我只是在这里更喜欢使用装饰器。

票数 5
EN

Stack Overflow用户

发布于 2015-12-08 16:31:56

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

票数 0
EN

Stack Overflow用户

发布于 2015-12-16 22:00:22

我会用动态导入来实现这一点。

models/regression/base.py

代码语言:javascript
复制
class Base(object):
    def get_type(self):
        return "BASE"

models/regression/subclass.py

代码语言:javascript
复制
from models.regression.base import Base

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

__myclass__ = SubClass

loader.py

代码语言:javascript
复制
from importlib import import_module

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

和空__init__.py文件中的models/models/regression/

通过以下方式:

代码语言:javascript
复制
nuitka --recurse-none --recurse-directory models --module loader.py

生成的loader.so包含models/子目录下的所有模块。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34160529

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档