【问题标题】:Using __new__ on classes derived from Django's models does not work在从 Django 模型派生的类上使用 __new__ 不起作用
【发布时间】:2012-02-01 17:31:31
【问题描述】:

这让我感到困惑,但我无法得到明确的答案。在从 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 框架而在幕后发生了一些类魔法)。

我们将不胜感激。

【问题讨论】:

  • 首先,你需要清理你的代码。在最后一个 sn-p 中,应该是 test 还是 Test?此外,您的缩进已关闭。
  • 是的,return super(test, self)... 应该是 return super(Test, sefl)...
  • 大家好 - 抱歉,我在编辑器中输入的示例非常快(我没有在编辑器中复制和粘贴代码) - 因此为不正确深表歉意。即使考虑到您的上述两个 cmets,它仍然不起作用。顺便说一句:我已经修复了上面的代码 sn-ps。
  • models.Model.__new__(Test, *args, **kwargs) 工作吗?
  • 不幸的是,我仍然得到同样的错误:(

标签: python django django-models


【解决方案1】:

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

这使得你引用的错误信息很奇怪;这通常是您在调用 unbound 方法(即通过访问 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 的实例,因为它和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 通过 class Test 调用它时,按照正常的实例创建协议,它会抱怨它没有得到 Test 的实例。

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

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

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

【讨论】:

  • 太好了,谢谢本。这正是我一直在寻找的答案。当我使用静态装饰器时,它现在可以完美运行。
  • __new__ 也可以是classmethod
  • @J.F.Sebastian:从逻辑上看,它显然应该是一个类方法。然而普通的__new__ 方法绝对是not 类方法,它们是静态方法。这又是一个历史性的怪癖,因为 __new__ 早于 classmethod 装饰器。鉴于__new__ 已经“特殊”,并且依赖于Python OO 模型的核心,我不会尝试在生产代码中使用__new__ 类方法,尽管它可能会很有效。
  • @Ben:这是对“必须是静态的”的评论,即__new__ staticmethod 但它可能 classmethod。 BDFL 说__new__classmethod 出现的原因。
  • @J.F.Sebastian 啊!我在该句子中使用“静态”仅表示“不对实例起作用”,但您说得对,这令人困惑。我已经改写了。
猜你喜欢
  • 1970-01-01
  • 2012-03-11
  • 2021-01-18
  • 2012-10-15
  • 1970-01-01
  • 1970-01-01
  • 2011-02-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多