在继承元类的类中调用 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 会引发错误的主要内容,如果未能解决你的问题,请参考以下文章

swift 元类构造方法

Python 元类中的继承如何工作?

错误:Implicit super constructor xx() is undefined for default constructor.

面向对象的深刻理解

在 Python 中,为啥为没有定义超类的类调用 super() 函数不是错误? [复制]

Typescript中的类