类继承与__new__

1 投票
2 回答
934 浏览
提问于 2025-04-17 19:09

我在理解类的 __metaclass__ 属性和实际的继承之间感到很困惑,还有 __new__ 方法在这两种情况下是怎么被调用的。我遇到的问题是因为在研究 Django 框架中的一些模型代码。

假设我想在子类的 Meta 子类中给一个类添加一个属性:

class Parent(type):

    def __new__(cls, name, base, attrs):
        meta = attrs.pop('Meta', None)
        new_class = super(Parent, cls).__new__(cls, name, base, attrs)
        new_class.fun = getattr(meta, 'funtime', None)
        return new_class

我不明白为什么 Django 的代码中实际调用了 __new__ 方法,但当我尝试写类似的代码时却不行。

根据我的经验,下面的代码实际上并没有调用父类的 __new__ 方法:

class Child(Parent):
    class Meta:
       funtime = 'yaaay'

C = Child()

当我尝试这样做时,它会报错,显示 TypeError:

TypeError: __new__() takes exactly 4 arguments (1 given)

不过我查看的源代码似乎是可以这样工作的。

我知道可以通过元类来实现这个功能:

class Child(object):
    __metaclass__ = Parent

但是我不明白为什么他们的方法对他们有效,而对我却不行,因为不使用 __metaclass__ 的方式在制作可分发模块时会更简洁。

有人能帮我指点一下我缺少了什么吗?

谢谢!

2 个回答

1

在一个扩展了 type 的元类中,__new__ 是用来创建类的。

在一个普通的类中,__new__ 是用来创建实例的。

元类就是用来创建类的类。你可能对类的继承和元类的概念感到困惑。

你的 Child 类继承了 Parent,你想创建一个 Child 的实例。但是,Parent 是一个元类,这意味着 Parent.__new__ 不应该用来创建类的实例。

1

在Django中,Model 其实不是一个元类。真正的元类是 ModelBase。这就是为什么他们的方法能奏效,而你的方法却不行的原因。
而且,最新版本的Django使用了一个辅助函数,six.with_metaclass,来包装 'ModelBase'。

如果我们想要遵循Django的风格,ParentChild 类看起来会像这样:

def with_metaclass(meta, base=object):
    """Create a base class with a metaclass."""
    return meta("NewBase", (base,), {})

class ParentBase(type):
    def __new__(cls, name, base, attrs):
        meta = attrs.pop('Meta', None)
        new_class = super(ParentBase, cls).__new__(cls, name, base, attrs)
        new_class.fun = getattr(meta, 'funtime', None)
        return new_class

class Parent(with_metaclass(ParentBase)):
    pass

class Child(Parent):
    class Meta:
       funtime = 'yaaay'

c = Child()

>>> c.fun
'yaaay'

我们先来关注一下 Parent。它几乎等同于:

NewBase = ParentBase("NewBase", (object,), {})
class Parent(NewBase):
    pass
    

关键在于如何理解 ParentBase("NewBase", (object,), {})
让我们回想一下 type() 的用法。

type(name, bases, dict)

这个函数有三个参数,返回一个新的类型对象。它本质上是类声明的一种动态形式。第一个参数是类名,成为 name 属性;第二个参数是基类的元组,成为 bases 属性;第三个参数是包含类体定义的字典,成为 dict 属性。例如,下面这两个语句创建的类型对象是完全相同的:

因为 ParentBase 是一个元类,属于 type 的子类。所以,ParentBase("NewBase", (object,), {})type("NewBase", (object,), {}) 非常相似。在这种情况下,唯一的区别是动态创建的类不是 type 的实例,而是 ParentBase 的实例。换句话说,NewBase 的元类是 ParentBaseParent 等同于:

class NewBase(object):
    __metaclass__ = ParentBase

class Parent(NewBase):
    pass
    

最后,我们得到了一个 __metaclass__

撰写回答