在继承元类的类中调用 super 会引发错误
Posted
技术标签:
【中文标题】在继承元类的类中调用 super 会引发错误【英文标题】:Calling super in a class inheriting a metaclass throws an error 【发布时间】:2018-11-08 08:59:39 【问题描述】:我正在尝试使用元类自动注册子类, 但是下面的代码在 class Dep_A 上抛出了错误
super(Dep_A,self).__init__().
*NameError: global name 'Dep_A' is not defined*
尝试了 MetaClass 中的注释行,但错误相同。 我知道这在不使用元类时有效,所以我如何初始化父类的 init 方法
registry =
def register(cls):
print cls.__name__
registry[cls.__name__] = cls()
return cls
class MetaClass(type):
def __new__(mcs, clsname, bases, attrs):
# newclass = super(MetaClass, mcs).__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__()
...
【问题讨论】:
所以你在registry
字典中存储了一个类的instance?
是的@Martijn,或者我应该存储类以便稍后实例化。
顺便说一句,您将在 2018 年为一个使用 Python 2 的高级 Python 功能的新项目奠定基础,该项目将在不到 2 年内达到 EOL。
DCC 软件约束,AutoDesk Maya 在 python27 上
【参考方案1】:
您正试图在class
语句完成之前创建类的实例。类对象已创建,但尚未分配给全局名称。
事件顺序为:
找到class Dep_A
语句,执行类体形成属性(创建__init__
函数)。
元类__new__
方法是使用类名、基类和类主体命名空间(分别为'Dep_A'
、(Department,)
和'__init__': <function ...>, ...
)调用的。
元类创建新的类对象
元类调用registry()
,传入新的类对象
registry()
调用类对象
类__init__
方法被调用
通常,当元类__new__
方法返回新创建的对象时,会分配Dep_A
。 在那之前没有分配全局名称Dep_A
,所以super(Dep_A, self).__init__()
调用失败。
您可以先从那里设置名称:
class MetaClass(type):
def __new__(mcs, clsname, bases, attrs):
# newclass = super(MetaClass, mcs).__new__(mcs, clsname, bases, attrs)
newclass = type.__new__(mcs,clsname, bases, attrs)
globals()[clsname] = newclass
register(newclass)
return newclass
或者,不要尝试在注册表中创建实例,而是存储一个类。您始终可以稍后创建单个实例。
以下实现了一个注册表,该注册表在您需要时透明地创建一个实例,并仅存储创建的一个实例:
from collections import Mapping
class Registry(Mapping):
def __init__(self):
self._classes =
self._instances =
def __getitem__(self, name):
if name not in self._instances:
self._instances[name] = self._classes.pop(name)()
return self._instances[name]
def __iter__(self):
return iter(self._classes.viewkeys() | self._instances.viewkeys())
def __len__(self):
return len(self._classes) + len(self._instances)
def register(self, cls):
self._classes[cls.__name__] = cls
registry = Registry()
class MetaClass(type):
def __new__(mcs, clsname, bases, attrs):
# newclass = super(MetaClass, mcs).__new__(mcs, clsname, bases, attrs)
newclass = type.__new__(mcs,clsname, bases, attrs)
registry.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__()
...
>>> registry.keys()
['Department', 'Dep_A']
>>> registry['Dep_A']
<__main__.Dep_A object at 0x110536ad0>
>>> vars(registry)
'_instances': 'Dep_A': <__main__.Dep_A object at 0x110536ad0>, '_classes': 'Department': <class '__main__.Department'>
【讨论】:
另一种方法很有启发性,谢谢!。顺便说一句,我正在使用这种技术来动态选择子类。这种做法会不会错? @river_bell:我不知道你的用例是什么,所以我不能说清楚,抱歉。【参考方案2】:您的register
函数试图在实际定义全局名称之前实例化Dep_A
。 (也就是说,它在类语句的主体仍在执行时被调用,在 Dep_A
被分配调用元类的结果之前。)
您想存储类本身:
def register(cls):
registry[cls.__name__] = cls # Not cls()
return cls
【讨论】:
以上是关于在继承元类的类中调用 super 会引发错误的主要内容,如果未能解决你的问题,请参考以下文章
错误:Implicit super constructor xx() is undefined for default constructor.