Python 3中,确定super().__new__是否与object.__new__相同?

13

假设我有一个调用 __new__ 的类,如何与 mro 协作,并根据需要调用超类的 __new__(带参数),但不使用额外的参数调用 object.__new__?例如,仅当您不向构造函数传递任何参数时,它才起作用:

class A(object):
    def __new__(cls, *args, **kwargs):
        print("A.__new__ called")
        return super(A, cls).__new__(cls, *args, **kwargs)

class B(object):
    def __new__(cls, *args, **kwargs):
        print("B.__new__ called")
        return super(B, cls).__new__(cls, *args, **kwargs)

class C(A, B): pass

>>> C.mro()
[__main__.C, __main__.A, __main__.B, builtins.object]
>>> C()
A.__new__ called
B.__new__ called

但是如果调用时带有参数,它将失败(因为在某个时刻Python更改为只接受调用类作为参数给object.__new__):

>>> C(1)
A.__new__ called
B.__new__ called
Traceback (most recent call last):
   ...
TypeError: object() takes no parameters
那么,AB如何确定它们的超类是object呢?我应该这样做吗:super(A, cls).__new__ is object.__new__?检查mro()[1] == builtins.object吗?还是我只需要决定"A将从不__new__中尝试友好地调用父类"(例如通过执行return object.__new__(cls))?
编辑:如果类定义了自定义的__init__方法,在Python 2.6和Python 2.7中可以正常工作,但在Python 3中仍然会不起作用

иҝҷжҳҜPythonеӨҡйҮҚ继жүҝе’Ңsuperзҡ„дёҖдёӘз»Ҹе…ёй—®йўҳгҖӮиҝҷйҮҢжңүдёҖдәӣжңүи¶Јзҡ„йҳ…иҜ»жқҗж–ҷгҖӮ - shx2
真正决定是否传递它自己使用和需要的参数取决于自定义的__new__方法。如果CBA都不需要参数,则object.__new__()引发异常是完全正确的事实。 - Martijn Pieters
请注意,只要您有一个自定义的__init__,那么object.__new__也不会引发异常。 - Martijn Pieters
1
一个(在我看来唯一明智的)解决方案是更改继承 DAG,使得这个问题不会出现。请参见Python的super()考虑超级! - user395760
2个回答

10

如果我们查看CPython源代码, 我们会发现:

  • object.__new__ 如果带有额外的参数,且 __new__ 被覆盖或者 __init__ 没被覆盖,将会报错; 同样地,
  • object.__init__ 如果带有额外的参数,且 __init__ 被覆盖或者 __new__ 没被覆盖,将会报错。

在2.x版本中,这个错误只是一个DeprecationWarning, 如果你在试验时可能会忽略它 (请注意,自从2.7和3.2版本,DeprecationWarning默认已被suppressed by default)。

这种行为的理由是类型要么是不可变的,此时应该在__new__中使用参数,要么是可变的,此时应该在__init__中使用参数。这似乎是实用性胜过纯洁性的情况;很难想象有多少有生产力的使用__new__委托给超类型(而不是仅仅记录/跟踪)的情况,而使用带有额外参数的object.__new__super调用错误的情况会更加普遍。
处理这个问题最干净的方法将是检查身份:super(A, cls).__new__ is object.__new__。理由如下:
  • this is how CPython decides whether to error (in C rather than Python, but the logic is the same):

    if (excess_args(args, kwds) &&
        (type->tp_init == object_init || type->tp_new != object_new)) {
        PyErr_SetString(PyExc_TypeError, "object() takes no parameters");
        return NULL;
    }
    
  • object.__new__ does nothing with the arguments, except to decide whether to error out.


你是指从Python翻译成C语言吗?这段代码看起来非常像C语言。 - Jeff Tratner
@JeffTratner е—ҜпјҢжҳҜзҡ„пјҢжҲ‘жғіиҜҙзҡ„жҳҜCPythonд»Јз ҒжҳҜCиҜӯиЁҖзӯүд»·дәҺдҪ еңЁPythonдёӯжүҖеҒҡзҡ„дәӢжғ…гҖӮ - ecatmur
我认为值得一提的是,object.__new__会检查抽象方法等内容,因此通常会调用它而不是使用PyType_GenericNew,否则将会破坏ABC的"保护"/强制执行。 - Flamefire

2

在it技术方面,真正决定是否传递参数的是自定义的__new__方法。如果CBA都不需要参数,则object.__new__()引发异常是完全正确的。

请注意,一旦您有一个自定义的__init__方法,那么object.__new__也不会引发异常。这是因为现在有地方可以将剩余的参数交给它了:

>>> class A(object):
...     def __new__(cls, *args, **kwargs):
...         print("A.__new__ called")
...         return super(A, cls).__new__(cls, *args, **kwargs)
... 
>>> class B(object):
...     def __new__(cls, *args, **kwargs):
...         print("B.__new__ called")
...         return super(B, cls).__new__(cls, *args, **kwargs)
... 
>>> class C(A, B):
...     def __init__(self, *args, **kwargs):
...         print args, kwargs
... 
>>> C(1)
A.__new__ called
B.__new__ called
(1,) {}
<__main__.C object at 0x10e7f9790>

看起来这种行为在Python 3中发生了改变。

可以测试以查看返回的__new__方法:

class B(object):
    def __new__(cls, *args, **kwargs):
        new = super(B, cls).__new__
        if new is object.__new__:
            return new(cls)
        return new(cls, *args, **kwargs)

1
自定义的 __init__ 不会阻塞它 - 您的示例 C 类没有子类化其他两个类,这就是为什么它可以工作的原因。如果您同时子类化两个类,您将会得到 TypeError - Jeff Tratner
这意味着你可以返回 object.__new__(cls),因为 Python 会随后调用 __init__ - Jeff Tratner
@JeffTratner:抱歉,那是个错误;使用正确的继承重新运行演示。在2.7中它对我有效。 - Martijn Pieters
@JeffTratner:什么不起作用?Martijn建议的自定义__init__在Python 3中对我来说很好用。 - Ethan Furman
2
更具体地说,在Python 3.3、3.4(以及3.5)中,即使类也有自定义的__init__,如果使用额外的参数调用object.__new__,它也会报错。 - dubiousjim
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接