继承元类的子类调用super触发NameError错误的解决办法
这个问题我之前也踩过坑!核心原因是元类的执行时机早于类名绑定到全局命名空间的时机,导致你在实例化子类的时候,类名还没被定义呢。
咱们先拆解下你的代码哪里出问题了:
当元类MetaClass的__new__方法创建完Dep_A这个类对象后,立刻调用了register(newclass),而register里直接执行了cls()——也就是实例化Dep_A。但这时候,Dep_A这个类对象还没被赋值到全局的Dep_A变量上(类定义的完整流程是:元类创建类对象 → 将类对象赋值给类名变量),所以在Dep_A.__init__里写super(Dep_A, self)时,Python找不到Dep_A这个全局名称,自然抛出NameError。
而不用元类的时候,你是先完成类定义(Dep_A已经绑定到全局),再手动实例化,所以super(Dep_A, self)能正常找到类名。
下面给你两个可行的解决方案:
方案一:延迟实例化(推荐)
不要在注册时直接创建类实例,而是把类本身存在注册表中,后续需要使用的时候再实例化。这样既避免了类定义未完成时的变量绑定问题,也更灵活。
修改后的完整代码:
registry = {} def register(cls): print(cls.__name__) registry[cls.__name__] = cls # 存储类对象,而非实例 return cls class MetaClass(type): def __new__(mcs, clsname, bases, attrs): newclass = type.__new__(mcs, clsname, bases, attrs) register(newclass) return newclass class Department(object): __metaclass__ = MetaClass def __init__(self): self.tasks = dict() class Dep_A(Department): def __init__(self): super(Dep_A, self).__init__() # 这里写你的其他初始化逻辑 # 后续需要使用实例时,从注册表中取出类再实例化 dep_a_instance = registry['Dep_A']()
方案二:修改super的调用方式(临时 workaround)
如果一定要在注册时就实例化子类,那可以在__init__里避免直接使用类名作为super的第一个参数。在Python 2里,你可以用self.__class__代替类名,或者直接显式调用父类的__init__方法:
registry = {} def register(cls): print(cls.__name__) registry[cls.__name__] = cls() # 保持即时实例化 return cls class MetaClass(type): def __new__(mcs, clsname, bases, attrs): newclass = type.__new__(mcs, clsname, bases, attrs) register(newclass) return newclass class Department(object): __metaclass__ = MetaClass def __init__(self): self.tasks = dict() class Dep_A(Department): def __init__(self): # 方式1:用self.__class__代替类名 super(self.__class__, self).__init__() # 方式2:直接调用父类的__init__(耦合性稍高,但更稳妥) # Department.__init__(self) # 这里写你的其他初始化逻辑
不过要注意,方式1如果遇到Dep_A的子类且子类没有重写__init__时,可能会触发无限递归,所以方式2更稳妥,但会和父类产生耦合。
总的来说,方案一是最优解,既符合元类注册的设计思路,也避免了潜在的问题。
内容的提问来源于stack exchange,提问作者river_bell

