我有一个工厂,如下代码所示:
class ClassFactory:
registry = {}
@classmethod
def register(cls, name):
def inner_wrapper(wrapped_class):
if name in cls.registry:
print(f'Class {name} already exists. Will replace it')
cls.registry[name] = wrapped_class
return wrapped_class
return inner_wrapper
@classmethod
def create_type(cls, name):
exec_class = cls.registry[name]
type = exec_class()
return type
@ClassFactory.register('Class 1')
class M1():
def __init__(self):
print ("Starting Class 1")
@ClassFactory.register('Class 2')
class M2():
def __init__(self):
print("Starting Class 2")
这很好,当我做时
if __name__ == '__main__':
print(ClassFactory.registry.keys())
foo = ClassFactory.create_type("Class 2")
我得到了dict_keys(['Class 1', 'Class 2']) Starting Class 2
的预期结果
现在的问题是,我想将类M1和M2隔离到它们自己的文件M1.py和M2.py中,并在将来以插件的方式使用它们自己的文档添加其他类。但是,只需将其放在自己的文件中m2.py
from test_ import ClassFactory
@MethodFactory.register('Class 2')
class M2():
def __init__(self):
print("Starting Class 2")
给出结果CCD_ 2,因为它从未注册过类。
所以我的问题是:当我想添加一个新的类时,我如何确保该类在不同于工厂的文件中注册,而不更改工厂文件?如何以这种方式自我注册?此外,这种装饰方式是做这种事情的好方法吗?或者有更好的实践吗?
感谢
当我想添加一个新类时,如何确保在不同于工厂的文件中注册该类,而不更改工厂文件?
我正在处理一个类似的问题,我找到了一个可能的解决方案。不过,这似乎太"黑客"了,所以在阅读我下面的建议时,请将你的批判性思维水平设置为"高":(
正如您在上面的一条评论中提到的,诀窍是强制加载包含单个类定义的单个*.py
文件。
将此应用于您的示例,这将涉及:
- 将所有类实现保存在特定文件夹中,例如,按如下方式构建文件:
.
└- factory.py # file with the ClassFactory class
└─ classes/
└- __init__.py
└- m1.py # file with M1 class
└- m2.py # file with M2 class
- 将以下语句添加到
factory.py
文件的末尾,该语句将负责加载和注册每个单独的类:
from classes import *
- 在
classes/
foder中的__init__.py
中添加一段类似以下代码片段的代码,以便动态加载所有类[1]:
from inspect import isclass
from pkgutil import iter_modules
from pathlib import Path
from importlib import import_module
# iterate through the modules in the current package
package_dir = Path(__file__).resolve().parent
for (_, module_name, _) in iter_modules([package_dir]):
# import the module and iterate through its attributes
module = import_module(f"{__name__}.{module_name}")
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if isclass(attribute):
# Add the class to this package's variables
globals()[attribute_name] = attribute
如果我运行你的测试代码,我会得到想要的结果:
# test.py
from factory import ClassFactory
if __name__ == "__main__":
print(ClassFactory.registry.keys())
foo = ClassFactory.create_type("Class 2")
$ python test.py
dict_keys(['Class 1', 'Class 2'])
Starting Class 2
此外,这种装饰器方式是做这种事情的好方法吗?或者有更好的实践吗?
很遗憾,我没有足够的经验来回答这个问题。然而,在寻找这个问题的答案时,我发现了以下可能对你有帮助的来源:
- [2]:这提出了一种基于Python元类的类存在注册方法。据我所知,它依赖于子类的注册,所以我不知道它在多大程度上适用于您的情况。我没有遵循这种方法,因为我注意到这本书的新版建议使用另一种技术(见下面的项目符号(
- [3] ,第49项:这是子类注册的"当前"建议,它依赖于基类中
__init_subclass__()
函数的定义
如果我必须将__init_subclass__()
方法应用于您的案例,我会执行以下操作:
- 将
Registrable
基类添加到factory.py
中(并稍微重新考虑ClassFactory
(,如下所示:
class Registrable:
def __init_subclass__(cls, name:str):
ClassFactory.register(name, cls)
class ClassFactory:
registry = {}
@classmethod
def register(cls, name:str, sub_class:Registrable):
if name in cls.registry:
print(f'Class {name} already exists. Will replace it')
cls.registry[name] = sub_class
@classmethod
def create_type(cls, name):
exec_class = cls.registry[name]
type = exec_class()
return type
from classes import *
- 稍微修改具体类以继承
Registrable
基类,例如:
from factory import Registrable
class M2(Registrable, name='Class 2'):
def __init__(self):
print ("Starting Class 2")