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

时间:2012-02-01 17:31:32

标签: python django django-models

这令我感到困惑,但我无法得到明确的答案。在从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 个答案:

答案 0 :(得分:11)

__new__未收到实例作为其第一个参数。怎么可能当(a)它是一个静态方法,如你所注意到的,以及(b)它的工作是创建一个实例并返回它! __new__的第一个参数通常称为cls,因为它是类。

这使得您引用的错误消息非常奇怪;它通常是您在使用该类的实例以外的其他内容调用未绑定方法(即通过访问ClassName.methodName获得的内容)时获得的错误消息{{1参数。但是,staticmethods(包括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__来构建Bar的实例,因为它和Foo.__new__最终都以相同的方式实现(将所有实际工作推迟到Bar.__new__)。 / p>

然而,这让我简单地看一下Django本身的源代码。 Django的object.__new__类有一个元类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)。

我现在99%确定问题就在这里,因为setattr是一个奇怪的隐式静态方法。因此,与其他所有静态方法不同,您尚未将__new__装饰器应用于它。 Python(在某种程度上)只识别staticmethod方法并将其作为静态方法处理而不是普通方法[1]。但我敢打赌,这只会在您在类块中定义__new__时发生,而不是在您使用__new__进行设置时。

因此,您的setattr(应该是静态方法但尚未由__new__装饰器处理)正在转换为普通实例方法。然后当Python调用它传递 staticmethod时,按照正常的实例创建协议,它会抱怨它没有获得Test的实例。

如果这一切都正确,那么:

  1. 这失败了,因为Django有点坏了,但只是因为它没有考虑到Python Test的静态不一致。
  2. 您可以通过将__new__应用于@staticmethod方法来完成此工作,即使您不应该这样做。

  3. [1]我认为这是Python的历史怪癖,因为__new__是在__new__装饰器之前引入的,但是staticmethod 不能采取一个实例,因为没有实例可以调用它。