You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何模板化使用__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))

解决方案

核心问题分析

  1. 子类共享注册表:父类的registered是类属性,子类继承后若未重新定义,会直接引用父类的字典,导致所有子类共享同一注册表。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 04:52:50