从Django模型派生的类使用__new__不起作用
这是让我困惑的东西,但我无法得到明确的答案。从DJango模型派生的类中使用__new__
方法(或更准确地说,静态方法)。从Django模型派生的类使用__new__不起作用
这是怎么__new__
应该理想地被(因为我们使用Django,我们可以假设蟒是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框架在幕后发生的)。
任何答案将不胜感激。
__new__
没有收到实例作为其第一个参数。 (a)它是一种静态方法,正如你注意到的,(b)它的工作是创建一个实例并返回它! __new__
的第一个参数通常被称为cls
,因为它是类。
这使得您引用的错误消息非常奇怪;当您将未绑定方法(即,您通过访问ClassName.methodName
)调用未绑定方法时,将该类的实例作为self
参数进行调用时,通常会出现错误消息。然而,staticmethods(包括__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
的实例,因为它和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__
是一个奇怪的隐含的静态方法。因此,与其他所有静态方法不同,您尚未将修饰器应用于其中。 Python(在某种程度上)只是识别__new__
方法,并将其作为静态方法而不是常规方法处理[1]。但我敢打赌,只有当您在类别块中定义__new__
时才会发生这种情况,而不是在您使用setattr
进行设置时发生。
因此,您的__new__
应该是一个静态方法,但尚未由装饰器处理,正在转换为常规实例方法。然后,当Python调用它通过类Test
,根据正常的实例创建协议,它抱怨说它没有得到Test
的实例。
如果这是正确的,那么:
- 失败的原因是Django是一个有点坏了,但只有在它未能采取Python的不一致有关
__new__
是静态的考虑。 - 即使您不需要,您也可以通过将
@staticmethod
应用于您的__new__
方法来完成此项工作。
[1]我相信这是一个Python的历史怪癖,因为__new__
是在staticmethod
装饰前出台,但__new__
不能采取一个实例,因为没有实例,就调用它。
+1很好完成。 – philofinfinitejest 2012-02-01 23:42:11
非常好,谢谢本。这正是我正在寻找的答案。现在当我使用静态装饰器时,它完美的工作。 – 2012-02-02 00:59:11
'__new__'也可以是'classmethod'。 – jfs 2012-02-03 01:28:16
首先,你需要清理你的代码。在最后一个片段中,它应该是“测试”还是“测试”?此外,您的缩进已关闭。 – Marcin 2012-02-01 17:37:00
是的,'超级超级(测试,自我)...'应该是'超级超级(Test,sefl)...'。 – Furbeenator 2012-02-01 17:39:35
嗨,大家好 - 抱歉,我在编辑器中很快打出了示例(我没有在那里复制和粘贴代码) - 所以不正确的道歉。即使您考虑了以上两条意见,它仍然不起作用。顺便说一句:我已经修复了上面的代码片断。 – 2012-02-01 17:42:32