在继承Django模型的类中使用__new__无效
这让我感到困惑,但我找不到明确的答案。这里说的是在从Django模型派生的类中使用__new__
方法(更准确地说,是静态方法)。
理想情况下,__new__
应该这样使用(因为我们在用Django,可以假设使用的是Python 2.x版本):
class A(object):
def __new__(self, *args, **kwargs):
print ("This is A's new function")
return super(A, self).__new__(self, *args, **kwargs)
def __init__(self):
print ("This is A's init function")
从上面的类实例化一个对象是按预期工作的。但是,当在从Django模型派生的类上尝试类似的操作时,就会发生一些意想不到的事情:
class Test(models.Model):
def __new__(self, *args, **kwargs):
return super(Test, self).__new__(self, *args, **kwargs)
从上面的类实例化一个对象时会出现这个错误:TypeError: unbound method __new__() must be called with Test instance as first argument (got ModelBase instance instead)
。
我不明白为什么会发生这种情况(虽然我知道由于Django框架的原因,背后发生了一些类的魔法)。
任何答案都将不胜感激。
1 个回答
__new__
方法的第一个参数并不是实例。因为(a)它是一个静态方法,正如你所提到的,(b) 它的任务是创建一个实例并返回它!__new__
的第一个参数通常叫做 cls
,因为它代表的是类。
这就让你引用的错误信息显得很奇怪;通常这是你在调用一个未绑定的方法(也就是通过 ClassName.methodName
访问到的)时,传入了一个不是该类实例的 self
参数时会出现的错误。然而,静态方法(包括 __new__
)并不会变成未绑定的方法,它们只是类的简单函数:
>>> class Foo(object):
def __new__(cls, *args, **kwargs):
return object.__new__(cls)
def method(self):
pass
>>> class Bar(object):
pass
>>> Foo.method
<unbound method Foo.method>
>>> Foo.__new__
<function __new__ at 0x0000000002DB1C88>
>>> Foo.method(Bar())
Traceback (most recent call last):
File "<pyshell#36>", line 1, in <module>
Foo.method(Bar())
TypeError: unbound method method() must be called with Foo instance as first argument (got Bar instance instead)
>>> Foo.__new__(Bar)
<__main__.Bar object at 0x0000000002DB4F28>
从这里可以看出,__new__
不应该是未绑定的方法。此外(与普通方法不同),它并不在乎你传入的参数是否一致;我实际上通过调用 Foo.__new__
成功构造了一个 Bar
的实例,因为 Foo.__new__
和 Bar.__new__
最终都是以相同的方式实现的(将所有实际工作推迟到 object.__new__
)。
不过,这让我简要查看了 Django 的源代码。Django 的 Model
类有一个元类 ModelBase
。这个元类相当复杂,我并没有完全搞明白它在做什么,但我注意到了一些很有趣的事情。
ModelBase.__new__
(这个元类的 __new__
方法,负责在你的类定义结束时创建类)在调用它的父类 __new__
时并没有传入你的类字典。它只传入了一个只有 __module__
属性的字典。然后,在进行了一系列处理后,它执行了以下操作:
# Add all attributes to the class.
for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj)
(attrs
是包含你在类定义中所有定义的字典,包括你的 __new__
方法;add_to_class
是一个元类方法,基本上就是 setattr
的功能)。
我现在99%确定问题出在这里,因为 __new__
是一个奇怪的隐式静态方法。所以与其他静态方法不同的是,你并没有给它加上 staticmethod
装饰器。Python(在某种程度上)只是识别了 __new__
方法,并将其作为静态方法处理,而不是普通方法[1]。但我敢打赌,这种情况只发生在你在类定义中定义 __new__
时,而不是通过 setattr
设置时。
所以你的 __new__
本该是一个静态方法,但没有被 staticmethod
装饰器处理,结果变成了普通的实例方法。当 Python 调用它并传入类 Test
时,按照正常的实例创建协议,它就会抱怨说没有得到 Test
的实例。
如果以上都正确,那么:
- 这个问题的出现是因为 Django 有点问题,但仅仅是因为它没有考虑到 Python 在
__new__
作为静态方法时的不一致性。 - 你可能可以通过给你的
__new__
方法加上@staticmethod
来解决这个问题,尽管你本不应该这样做。
[1] 我认为这是 Python 的一个历史遗留问题,因为 __new__
在 staticmethod
装饰器出现之前就已经引入了,但 __new__
不能 接受一个实例,因为没有实例可以调用它。