python:元类中__new__的调用时机

4 投票
1 回答
577 浏览
提问于 2025-04-16 04:12

下面的代码无法编译,它提示:

NameError: name 'fields' is not defined

在最后一行。是不是因为 __new__ 这个方法要等到 fields 赋值后才被调用?我该怎么办呢?

class Meta(type):
    def __new__(mcs, name, bases, attr):
        attr['fields'] = {}
        return type.__new__(mcs, name, bases, attr)

class A(metaclass = Meta):
    def __init__(self, name):
        pass

class B(A):
    fields['key'] = 'value'

补充:

我发现这不是时间上的问题,而是名字隐藏的问题。如果我直接写 A.fields,就没问题了。

我想知道为什么我不能使用 fields 或者 super().fields

1 个回答

4

在执行 fields['key'] = 'value' 这行代码之前,元类的机制还没有开始工作。

class foo(object):
    var1 = 'bar'

    def foobar(self):
        pass

当 Python 遇到 class 这个声明时,它会进入一个新的局部命名空间。

  1. 首先,它会执行 var1 = 'bar' 这行代码。这相当于 locals()['var1'] = 'bar'

  2. 接着,它会执行 def foobar 这行代码。这相当于 locals()['var'] = 编译函数的结果

  3. 然后,它会把 locals()、类名、继承的类和元类一起传给元类的 __new__ 方法。在这个例子中,元类就是 type

  4. 最后,它会退出这个新的局部命名空间,把从 __new__ 返回的类对象放到外部命名空间中,并命名为 foo

当你使用 A.fields 时,代码是可以工作的,因为类 A 已经被创建,以上的过程也已经执行过,你的 MetaA 中安装了 fields

你不能使用 super().fields,因为类名 B 在 super 运行时还没有定义。也就是说,你需要用 super(B).fields,但 B 是在类创建之后才定义的。

更新

这里有一些代码,可以根据你对我评论的回复来实现你想要的功能。

def MakeFields(**fields):
    return fields

class Meta(type):
    def __new__(mcs, name, bases, attr):
        for base in bases:
            if hasattr(base, 'fields'):
                inherited = getattr(base, 'fields')
                try:
                    attr['fields'].update(inherited)
                except KeyError:
                    attr['fields'] = inherited
                except ValueError:
                    pass
        return type.__new__(mcs, name, bases, attr)

class A(metaclass=Meta):
    fields = MakeFields(id='int',name='varchar') 

class B(A):
    fields = MakeFields(count='int')

class C(B):
    pass

class Test(object):
    fields = "asd"

class D(C, Test):
    pass

print C.fields
print D.fields

撰写回答