如果__new__没有返回cls的实例,python不会调用__init__这个事实的设计原因是什么?

时间:2015-01-18 00:39:48

标签: python

关于为什么python在创建对象后并不总是调用__init__的问题有很多问题。 当然,答案可以在文档的摘录中找到:

  

如果__new__()返回cls的实例,则会调用新实例的__init__()方法,如__init__(self[, ...]),其中self是新实例,其余参数与传递给__new__()的参数相同。

     

如果__new__()未返回cls的实例,则不会调用新实例的__init__()方法。

这个的设计原因是什么?

4 个答案:

答案 0 :(得分:2)

__init__确实像构造函数一样。它需要一个实例来完成其工作,如设置属性等。如果__new__未显式返回显式返回实例,则默认返回None

想象一下当__init__获得None作为输入并尝试设置属性时会发生什么?它会引发一个名为"AttributeError: 'NoneType' object has no attribute xxxxx".

的异常

所以我认为当__init__返回None时不调用__new__是很自然的。

答案 1 :(得分:2)

在Python 2中,你实际上不能调用常规方法,第一个参数不是类的实例(或子类):

class Foo(object):
    def __init__(self):
        pass

Foo.__init__()
# TypeError: unbound method __init__() must be called with Foo instance as first argument (got nothing instead)

Foo.__init__(3)
# TypeError: unbound method __init__() must be called with Foo instance as first argument (got int instance instead)

所以__init__没有被调用,因为它不能可能做除了立即引发异常之外的任何事情。不试图调用它是非常有用的(虽然我不认为我曾经见过代码利用这个)。

Python 3有一个稍微简单的方法实现,这个限制不再适用,但__new__语义是相同的。无论如何,尝试在异物上运行类初始化器并没有多大意义。


对于一个更有代表性的答案,而不是一个"因为它是这样的"回答:

覆盖__new__已经很奇怪了。默认情况下,它返回一个未初始化的对象,这是Python非常难以隐藏的概念。如果你覆盖它,你可能会做这样的事情:

class Foo(object):
    def __new__(cls, some_arg):
        if some_arg == 15:
            # 15 is a magic number for some reason!
            return Bar()
        else:
            return super(Foo, cls).__new__(cls, some_arg)

让我们设想一个Python变体,它无条件地在返回值上调用__init__。我立即看到了一些问题。

当你返回Bar()时,Python应该调用Bar.__init__(在这种情况下已经调用过)或Foo.__init__(这是一个完全不同的类型,会破坏任何保证{ {1}}制作)?

答案肯定是Bar被调用。这是否意味着您必须使用满口的Bar.__init__返回未初始化的Bar实例? Python很少要求你在使用return Bar.__new__(Bar)之外调用dunder方法,所以这很不寻常。

super的论据来自哪里? Bar.__init__Foo.__new__都传递相同的参数 - 传递给Foo.__init__的参数,即处理type.__call__的参数。但是,如果您明确地呼叫Foo(...),则无法记住您想要传递给Bar.__new__的参数。您无法将它们存储在新的Bar.__init__对象上,因为Bar应该做什么!如果您只是说它获得了传递给Bar.__init__的相同参数,那么您将严格限制从Foo返回的类型。

或者,如果您想要返回已存在的对象,该怎么办? Python没有办法表明对象已经"已经"已初始化 - 因为未初始化的对象只是__new__感兴趣的瞬态和大部分内部事物 - 因此您无法拒绝再次调用__new__

目前的方法有点笨拙,但我认为没有更好的选择。 __init__ 假设为新对象创建存储空间,并且完全返回不同类型的对象只是一件非常奇怪的事情;这是Python可以处理它的最不令人惊讶和最有用的方式。

如果此限制妨碍您,请记住整个__new____new__舞蹈只是__init__的行为。您可以完全自由地在元类上定义自己的type.__call__行为,或者只是将您的类换成工厂函数。

答案 2 :(得分:1)

这可能不是设计理由,但如果这个设计决定是一个类不能返回自己的实例,那么这是一个很好的结果:

class FactoryClass():
    def __new__(klass, *args, **kwargs):
        if args or kwargs:
            return OtherClass(*args, **kwargs)
        return super().__new__(klass)

这有时候很有用。显然,如果您要返回其他类对象,则不希望调用工厂类的init方法。

我希望上面的例子说明的真正原因和底线是任何其他设计决策都是荒谬的。 New是Python类对象的构造函数,而不是init。 Init与任何其他类方法没有什么不同(除了在构造对象后自动神奇地调用它);是否有人被调用取决于构造函数中发生的事情。这只是一种自然的做事方式。任何其他方式都会被打破。

答案 3 :(得分:0)

如果__new__未返回cls的实例,则将返回值传递给cls.__init__可能会导致非常糟糕的事情。

特别是,Python不要求你返回相同类型的实例。

采取这个人为的例子:

In [1]: class SillyInt(int):
   ...:     def __new__(cls):
   ...:         return "hello"
   ...:     

In [2]: si = SillyInt()

In [3]: si
Out[3]: 'hello'

所以我们创建了int的子类,但我们的__new__方法返回了一个str对象。

然后将 string 传递给我们继承的int.__init__方法进行实例化是没有多大意义的。 即使在所讨论的特定情况下,它“有效”(在不抛出任何错误的意义上),我们也可能创建了一个处于不一致状态的对象。


好的,那么更具体(尽管仍然做作)的例子呢?

假设我们创建了两个类,这些类在初始化时设置了相同的属性(在__init__方法中):

In [1]: class Tree():
   ...:     def __init__(self):
   ...:         self.desc = "Tree"
   ...:         

In [2]: class Branch():
   ...:     def __init__(self):
   ...:         self.desc = "Branch"
   ...:         

如果我们现在创建一个子类,在自定义__new__方法中,它想要返回一个不同类型的对象(有时在实践中有意义,即使它不在这里!):

In [3]: class SillyTree(Tree):
   ...:     def __new__(cls):
   ...:         return Branch()
   ...:     

在实例化这个子类时,我们可以看到我们有一个期望类型的对象,并且Branch初始化器是被调用的对象:

In [4]: sillyTree = SillyTree()

In [5]: sillyTree.desc
Out[5]: 'Branch'

如果Python无条件地调用继承的初始化器,会发生什么?

好吧,我们实际上可以直接调用初始化程序来测试它。

我们最终得到的是Branch的一个实例,它被初始化为Tree,使对象处于非常意外的状态:

In [6]: SillyTree.__init__(sillyTree)

In [7]: sillyTree.desc
Out[7]: 'Tree'

In [8]: isinstance(sillyTree, Branch)
Out[8]: True

In [9]: isinstance(sillyTree, Tree)
Out[9]: False