如何模板化使用__new__作为工厂的Registry类?
问题描述
我写了一个BaseRegistry类,用类方法做装饰器,把其他类以字符串名称注册到类属性字典registered里。这个字典会在__new__方法里根据传入的字符串名称返回对应的类。这个实现本身没问题,但现在我想创建多个这样的Registry又不想重复代码。但如果继承BaseRegistry,所有子类会共享同一个字典,这不是我想要的。我找不到实现这种“模板化”的方法。
原代码示例:
class BaseRegistry: registered = {} @classmethod def add(cls, name): def decorator(function): cls.registered[name] = function return function return decorator def __new__(cls, name): return cls.registered[name] class RegistryOne(BaseRegistry): pass class RegistryTwo(BaseRegistry): pass @RegistryOne.add("the_one") class Example1: def __init__(self, scale_factor=1): self.scale_factor = scale_factor @RegistryOne.add("the_two") class Example2: def __init__(self, scale_factor=2): self.scale_factor = scale_factor if __name__ == "__main__": the_one = RegistryOne("the_one")() print(f"{the_one.scale_factor=}") assert RegistryOne.registered != RegistryTwo.registered
编辑说明:在这个实现里,Pylint对the_one = RegistryOne("the_one")()这行代码发出警告,提示信息为:
E1102: RegistryOne('the_one') is not callable (not-callable)
(翻译:E1102: RegistryOne('the_one') 不可调用(not-callable))
解决方案
核心问题分析
- 子类共享注册表:父类的
registered是类属性,子类继承后若未重新定义,会直接引用父类的字典,导致所有子类共享同一注册表。 - Pylint警告原因:
__new__方法返回的是注册的类,但Pylint默认认为RegistryOne("the_one")返回的是BaseRegistry实例,从而判定其不可调用。
推荐实现:用__init_subclass__创建独立注册表
利用Python 3.6+的__init_subclass__特性,让每个子类自动生成独立的注册表,同时简化实例化逻辑消除警告。
修改后的代码:
class BaseRegistry: def __init_subclass__(cls): # 每个子类创建时初始化专属的registered字典 cls.registered = {} @classmethod def add(cls, name): def decorator(cls_to_register): cls.registered[name] = cls_to_register return cls_to_register return decorator def __new__(cls, name, *args, **kwargs): # 直接返回注册类的实例,无需额外调用() return cls.registered[name](*args, **kwargs) class RegistryOne(BaseRegistry): pass class RegistryTwo(BaseRegistry): pass @RegistryOne.add("the_one") class Example1: def __init__(self, scale_factor=1): self.scale_factor = scale_factor @RegistryOne.add("the_two") class Example2: def __init__(self, scale_factor=2): self.scale_factor = scale_factor if __name__ == "__main__": # 直接调用RegistryOne获取实例,简化写法 the_one = RegistryOne("the_one") print(f"{the_one.scale_factor=}") # 支持传递初始化参数 the_two = RegistryOne("the_two", scale_factor=5) print(f"{the_two.scale_factor=}") # 验证两个注册表完全独立 assert RegistryOne.registered != RegistryTwo.registered assert "the_one" not in RegistryTwo.registered
方案细节说明
__init_subclass__的作用:子类继承BaseRegistry时,该方法会自动执行,给每个子类创建独立的registered字典,彻底解决共享问题。- 简化实例化流程:修改
__new__使其直接返回注册类的实例,同时支持传递初始化参数,既简化了调用代码,也让Pylint能正确识别返回类型,消除警告。 - 兼容原有装饰器逻辑:
add装饰器无需修改,依然可以正常将类注册到对应Registry的专属字典中。
兼容低版本Python的方案:使用元类
如果需要支持Python 3.6以下版本,可以用元类实现相同效果:
class RegistryMeta(type): def __new__(meta, name, bases, attrs): cls = super().__new__(meta, name, bases, attrs) # 为每个新类初始化独立注册表 cls.registered = {} return cls class BaseRegistry(metaclass=RegistryMeta): @classmethod def add(cls, name): def decorator(cls_to_register): cls.registered[name] = cls_to_register return cls_to_register return decorator def __new__(cls, name, *args, **kwargs): return cls.registered[name](*args, **kwargs) # 子类定义和使用方式与推荐方案一致
内容的提问来源于stack exchange,提问作者PhML
相关产品推荐
相关产品推荐

