从Django模型派生的类使用__new__不起作用

从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框架在幕后发生的)。

任何答案将不胜感激。

+1

首先,你需要清理你的代码。在最后一个片段中,它应该是“测试”还是“测试”?此外,您的缩进已关闭。 – Marcin 2012-02-01 17:37:00

+0

是的,'超级超级(测试,自我)...'应该是'超级超级(Test,sefl)...'。 – Furbeenator 2012-02-01 17:39:35

+0

嗨,大家好 - 抱歉,我在编辑器中很快打出了示例(我没有在那里复制和粘贴代码) - 所以不正确的道歉。即使您考虑了以上两条意见,它仍然不起作用。顺便说一句:我已经修复了上面的代码片断。 – 2012-02-01 17:42:32

__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的实例。

如果这是正确的,那么:

  1. 失败的原因是Django是一个有点坏了,但只有在它未能采取Python的不一致有关__new__是静态的考虑。
  2. 即使您不需要,您也可以通过将@staticmethod应用于您的__new__方法来完成此项工作。

[1]我相信这是一个Python的历史怪癖,因为__new__是在staticmethod装饰前出台,但__new__不能采取一个实例,因为没有实例,就调用它。

+0

+1很好完成。 – philofinfinitejest 2012-02-01 23:42:11

+0

非常好,谢谢本。这正是我正在寻找的答案。现在当我使用静态装饰器时,它完美的工作。 – 2012-02-02 00:59:11

+0

'__new__'也可以是'classmethod'。 – jfs 2012-02-03 01:28:16