主要目标:通过一个字符串自动将类注册到工厂中,在运行时使用该字符串动态创建类,这些类可以在它们自己的文件中而不是分组在一个文件中。
我有几个类都继承自相同的基类,并且它们定义了一个字符串作为它们的类型。
用户想要获取其中一个类的实例,但只知道该类型在运行时。
因此,我有一个工厂来创建给定类型的实例。我不想硬编码"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__
变量,以包含所有子类。我不想硬编码子类,因为这会破坏使用元类的目的。
我可以使用以下方法来检查包中的文件:
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: 每个子类必须在一个文件中。
- 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"
运行它,你只能看到打印出 "Base"。使用装饰器会得到相同的结果。
.so
文件时,您是什么意思?您如何“编译”该项目?您能否修改编译过程以在包的__init__.py
文件中生成__all__
? - jmepkgutil
的iter_modules
应该可以工作。因此,想法是编写一个快速脚本,将纯Python包安装到虚拟环境中,导入它,通过pkgutil
检查它,然后生成一个__init__.py
文件。我将通过编辑我的答案来演示我的意思。 - jme