主要目标:在工厂中自动注册类(通过字符串),以便在运行时使用该字符串动态创建,类可以位于它们自己的文件中,而不是分组在一个文件中。
我有几个类,它们都继承自同一个基类,并且它们定义一个字符串作为它们的类型。
用户想要获取这些类之一的实例,但只在运行时知道类型。
因此,我有一个工厂来创建给定类型的实例。我不想硬编码“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)
Run Code Online (Sandbox Code Playgroud)
问题是,要使其工作,工厂必须导入所有子类(因此元类运行)。要解决这个问题,您可以使用from X import *
但要使其工作,您需要__all__在包的文件中定义一个 var__init__.py以包含所有子类。
我不想对子类进行硬编码,因为它违背了使用元类的目的。
我可以使用以下命令查看包中的文件:
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)]
Run Code Online (Sandbox Code Playgroud)
效果很好,但是项目需要编译为单个 .so 文件,这会导致文件系统的使用无效。
那么,如何实现在运行时创建实例的主要目标,而不需要对类型进行硬编码呢?
有没有一种方法可以__all__在运行时填充 var 而无需触及文件系统?
在Java中,我可能会用注释来装饰类,然后在运行时获取带有该注释的所有类,Python上有类似的东西吗?
我知道 python 中有装饰器,但我不确定是否可以以这种方式使用它们。
编辑1:每个子类必须位于一个文件中:
- Models
-- __init__.py
-- ModelFactory.py
-- Regression
--- __init__.py
--- Base.py
--- Subclass1.py
--- Subclass2ExtendsSubclass1.py
Run Code Online (Sandbox Code Playgroud)
编辑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"
Run Code Online (Sandbox Code Playgroud)
运行它你只能看到它打印的“Base”。使用装饰器可以得到相同的结果。
将类注册为运行时的一种简单方法是使用装饰器:
\n\nregistry = {}\n\ndef register(cls):\n registry[cls.__name__] = cls\n return cls\n\n@register\nclass Foo(object):\n pass\n\n@register\nclass Bar(object):\n pass\nRun Code Online (Sandbox Code Playgroud)\n\n如果所有类都在同一个模块中定义,并且该模块是在运行时导入的,那么这将起作用。然而,你的情况使事情变得复杂。首先,您想在不同的模块中定义您的类。这意味着我们必须能够在运行时动态确定包中存在哪些模块。使用 Python 的pkgutil模块这会很简单,但是,您还声明您正在使用 Nuitka 将包编译成扩展模块。pkgutil不适用于此类扩展模块。
我找不到任何记录的方法来确定 Python 中 Nuitka 扩展模块中包含的模块。如果确实存在,则上面的装饰器方法将在动态导入每个子模块后起作用。
\n\n事实上,我认为最直接的解决方案是编写一个脚本来__init__.py在编译之前生成一个。假设我们有以下包结构:
.\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 plugins\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 alpha.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 beta.py\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 register.py\nRun Code Online (Sandbox Code Playgroud)\n\n“插件”包含在该plugins目录中。文件的内容是:
# register.py\n# -----------\n\nregistry = {}\ndef register(cls):\n registry[cls.__name__] = cls\n return cls\n\n# __init__.py\n# -----------\n\nfrom . import plugins\nfrom . import register\n\n\n# ./plugins/alpha.py\n# ------------------\n\nfrom ..register import register\n\n@register\nclass Alpha(object):\n pass\n\n\n# ./plugins/beta.py\n# ------------------\n\nfrom ..register import register\n\n@register\nclass Beta(object):\n pass\nRun Code Online (Sandbox Code Playgroud)\n\n按照目前的情况,导入上面的包不会导致任何类被注册。这是因为类定义永远不会运行,因为包含它们的模块永远不会导入。补救措施是自动__init__.py为该plugins文件夹生成一个。下面是一个完全执行此操作的脚本——该脚本可以成为编译过程的一部分。
import pathlib\n\n\nroot = pathlib.Path(\'./mypkg/plugins\')\nexclude = {\'__init__.py\'}\n\ndef gen_modules(root):\n for entry in root.iterdir():\n if entry.suffix == \'.py\' and entry.name not in exclude:\n yield entry.stem\n\nwith (root / \'__init__.py\').open(\'w\') as fh:\n for module in gen_modules(root):\n fh.write(\'from . import %s\\n\' % module)\nRun Code Online (Sandbox Code Playgroud)\n\n将此脚本放在包根目录上方的一个目录(假设您的包名为mypkg)并运行它会产生:
from . import alpha\nfrom . import beta\nRun Code Online (Sandbox Code Playgroud)\n\n现在进行测试:我们编译包:
\n\nnuitka --module mypkg --recurse-to=mypkg\nRun Code Online (Sandbox Code Playgroud)\n\n并尝试导入它,检查所有类是否已正确注册:
\n\n>>> import mypkg\n>>> mypkg.register.registry\n{\'Beta\': <class \'mypkg.plugins.beta.Beta\'>, \n \'Alpha\': <class \'mypkg.plugins.alpha.Alpha\'>}\nRun Code Online (Sandbox Code Playgroud)\n\n请注意,使用元类注册插件类也可以使用相同的方法,我只是更喜欢在这里使用装饰器。
\n| 归档时间: |
|
| 查看次数: |
2308 次 |
| 最近记录: |