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

Bar*_*eyn 6 python django django-models

这令我感到困惑,但我无法得到明确的答案.__new__在从DJango模型派生的类中使用该方法(或更准确地说,静态方法).

这是__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")
Run Code Online (Sandbox Code Playgroud)

从上面的类实例化对象按预期工作.现在,当在Django模型派生的类上尝试这样的事情时,会发生意外情况:

class Test(models.Model):
  def __new__(self, *args, **kwargs):
    return super(Test, self).__new__(self, *args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

从上面的类中实例化对象会导致此错误: TypeError: unbound method __new__() must be called with Test instance as first argument (got ModelBase instance instead).

我无法理解为什么会发生这种情况(虽然我知道由于Django框架,一些类魔法正在幕后发生).

任何答案将不胜感激.

Ben*_*Ben 11

__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>
Run Code Online (Sandbox Code Playgroud)

你可以从中看到,__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)
Run Code Online (Sandbox Code Playgroud)

(attrs是包含您的类块中所有定义的字典,包括您的__new__函数; add_to_class是一个主要是元类的方法setattr).

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

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

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

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

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