类继承与__new__
我在理解类的 __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 个回答
在一个扩展了 type
的元类中,__new__
是用来创建类的。
在一个普通的类中,__new__
是用来创建实例的。
元类就是用来创建类的类。你可能对类的继承和元类的概念感到困惑。
你的 Child
类继承了 Parent
,你想创建一个 Child
的实例。但是,Parent
是一个元类,这意味着 Parent.__new__
不应该用来创建类的实例。
在Django中,Model
其实不是一个元类。真正的元类是 ModelBase
。这就是为什么他们的方法能奏效,而你的方法却不行的原因。
而且,最新版本的Django使用了一个辅助函数,six.with_metaclass
,来包装 'ModelBase'。
如果我们想要遵循Django的风格,Parent
和 Child
类看起来会像这样:
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
的元类是 ParentBase
。Parent
等同于:
class NewBase(object):
__metaclass__ = ParentBase
class Parent(NewBase):
pass
最后,我们得到了一个 __metaclass__
。