使用不同文件中的类向工厂注册类



我有一个工厂,如下代码所示:

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文件。

将此应用于您的示例,这将涉及:

  1. 将所有类实现保存在特定文件夹中,例如,按如下方式构建文件:
.
└- factory.py     # file with the ClassFactory class
└─ classes/
└- __init__.py
└- m1.py        # file with M1 class
└- m2.py        # file with M2 class
  1. 将以下语句添加到factory.py文件的末尾,该语句将负责加载和注册每个单独的类:
from classes import *
  1. 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__()方法应用于您的案例,我会执行以下操作:

  1. 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 *
  1. 稍微修改具体类以继承Registrable基类,例如:
from factory import Registrable
class M2(Registrable, name='Class 2'):
def __init__(self):
print ("Starting Class 2")

最新更新