Python(和Python C API):__new__与__init__


126

我要问的问题似乎是Python对__new__和__init__的重复使用?,但无论如何,我仍然不清楚__new__和之间的实际区别是什么__init__

在您急于告诉我__new__创建对象和__init__初始化对象之前,请让我明确:我明白了。 实际上,这种区分对我来说是很自然的,因为我在C ++中有经验,在那里我们放置了new,它类似地将对象分配与初始化分开。

Python的C API教程解释它是这样的:

新成员负责创建(而不是初始化)该类型的对象。它在Python中作为__new__()方法公开。... 实施新方法的原因之一是要确保实例变量的初始值

所以,是的-我明白__new__,但是尽管如此,我仍然不明白为什么它在Python中很有用。给出的示例说,__new__如果要“确保实例变量的初始值” ,这可能会很有用。好吧,这不正是要做__init__什么吗?

在C API教程中,显示​​了一个示例,其中创建了新的Type(称为“ Noddy”),并__new__定义了Type的功能。Noddy类型包含一个名为的字符串成员first,并且该字符串成员被初始化为一个空字符串,如下所示:

static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    .....

    self->first = PyString_FromString("");
    if (self->first == NULL)
    {
       Py_DECREF(self);
       return NULL;
    }

    .....
}

请注意,如果没有在此__new__定义的方法,我们将不得不使用PyType_GenericNew,它只会将所有实例变量成员初始化为NULL。因此,该__new__方法的唯一好处是实例变量将从一个空字符串开始,而不是NULL。 但是,为什么这会有用呢,因为如果我们要确保将实例变量初始化为某个默认值,那么我们可以在__init__方法中做到这一点?

Answers:


137

差异主要发生在可变与不可变类型之间。

__new__接受一个类型作为第一个参数,并且(通常)返回该类型的新实例。因此,它适用于可变类型和不可变类型。

__init__接受一个实例作为第一个参数,并修改该实例的属性。这不适用于不可变类型,因为它允许在创建后通过调用修改它们obj.__init__(*args)

比较的行为tuplelist

>>> x = (1, 2)
>>> x
(1, 2)
>>> x.__init__([3, 4])
>>> x # tuple.__init__ does nothing
(1, 2)
>>> y = [1, 2]
>>> y
[1, 2]
>>> y.__init__([3, 4])
>>> y # list.__init__ reinitialises the object
[3, 4]

关于它们为什么分开的原因(除了简单的历史原因):__new__方法需要一堆样板才能正确(最初的对象创建,然后记得最后返回对象)。__init__相比之下,方法非常简单,因为您只需设置需要设置的任何属性即可。

除了__init__更易于编写的方法以及上面提到的可变与不可变的区别外,还可以利用这种分离,__init__通过在中设置任何绝对必要的实例不变式,使在子类中调用父类成为可选的__new__。不过,这通常是一种可疑的做法-通常在需要时仅调用父类__init__方法会更清晰。


1
您称为“样板”的代码__new__不是样板,因为样板永远不会改变。有时您需要用不同的内容替换该特定代码。
Miles Rout

13
创建或以其他方式获取实例(通常是通过super调用)并返回实例是任何__new__实现的必要部分,也是我所指的“样板”。相比之下,pass是一个有效的实现__init__-没有任何必需的行为。
ncoghlan 2013年

37

可能还有其他用途,__new__但有一个真正显而易见的用途:如果不使用,就不能继承不可变类型__new__。例如,假设您要创建一个元组的子类,该子类只能包含0到之间的整数值size

class ModularTuple(tuple):
    def __new__(cls, tup, size=100):
        tup = (int(x) % size for x in tup)
        return super(ModularTuple, cls).__new__(cls, tup)

你根本无法做到这一点__init__-如果你试图修改self__init__,解释器会抱怨你试图修改不可变对象。


1
我不明白为什么要使用超级?我的意思是为什么new应该返回超类的实例?此外,正如您所说,为什么我们应该将cls明确传递给new?super(ModularTuple,cls)不返回绑定方法吗?
奥尔科特

3
@Alcott,我认为您误会了__new__。我们cls明确地传递给,__new__因为,正如您在这里 可以读到的那样__new__ 始终需要将类型作为其第一个参数。然后,它返回该类型的实例。因此,我们没有返回超类的实例,而是返回的实例cls。在这种情况下,就像我们说过的一样tuple.__new__(ModularTuple, tup)
senderle's

35

__new__()可以返回与其绑定的类不同类型的对象。__init__()仅初始化该类的现有实例。

>>> class C(object):
...   def __new__(cls):
...     return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5

到目前为止,这是最简洁的解释。
塔里克

不太正确。我有__init__包含看起来像的代码的方法self.__class__ = type(...)。这导致该对象与您认为正在创建的对象属于不同的类。我实际上不能int像您所做的那样将其更改为...。我收到有关堆类型或类似内容的错误...但是我将其分配给动态创建的类的示例有效。
ArtOfWarfare

我也对何时__init__()调用感到困惑。例如,在lonetwin的答案,无论是Triangle.__init__()Square.__init__()自动获得取决于哪种类型的所谓__new__()的回报。从你在你的答案说(我读过这在其他地方),它看起来像任何一方的不应该是因为Shape.__new__() 返回的实例cls(也不返回其子类之一)。
martineau

1
@martineau:__init__()lonetwin的答案中的方法在实例化各个对象时(即,当它们的 __new__()方法返回时)而不是在Shape.__new__()返回时被调用。
伊格纳西奥·巴斯克斯

啊,对,Shape.__init__()(如果有的话)不会被调用。现在,一切都变得更加有意义了:¬)
martineau

13

这不是一个完整的答案,但也许可以说明差异。

__new__当必须创建一个对象时,它将总是被调用。在某些情况下__init__不会被呼叫。一个示例是,当您从pickle文件中解开对象时,它们将被分配(__new__)但未初始化(__init__)。


如果我想分配内存并初始化数据,我会从new调用init吗?为什么创建实例init时如果new不存在?
redpix_ 2015年

2
__new__方法的工作是创建(这意味着内存分配)该类的实例并返回它。初始化是一个单独的步骤,通常是用户可见的。如果您遇到特定问题,请询问另一个问题。
Noufal Ibrahim 2015年

3

只是想添加一个关于定义vs 的意图(与行为相反)的词__new____init__

当我试图理解定义类工厂的最佳方法时,我遇到了这个问题。我意识到,在__new__概念上与之不同的一种方式__init__是,这样的好处__new__恰恰是问题中所陈述的事实:

因此__new__方法的唯一好处是实例变量将从一个空字符串开始,而不是NULL。但是为什么这会有用呢,因为如果我们要确保实例变量被初始化为某个默认值,那么我们可以在__init__方法中做到这一点?

考虑到上述情况,当实例实际上是类本身时,我们关心实例变量的初始值。因此,如果我们在运行时动态创建一个类对象,并且需要定义/控制一些有关正在创建的类的后续实例的特殊操作,则可以在__new__元类的方法中定义这些条件/属性。

我一直对此感到困惑,直到我真正考虑到该概念的应用,而不仅仅是其含义。这是一个希望可以使区别清楚的示例:

a = Shape(sides=3, base=2, height=12)
b = Shape(sides=4, length=2)
print(a.area())
print(b.area())

# I want `a` and `b` to be an instances of either of 'Square' or 'Triangle'
# depending on number of sides and also the `.area()` method to do the right
# thing. How do I do that without creating a Shape class with all the
# methods having a bunch of `if`s ? Here is one possibility

class Shape:
    def __new__(cls, sides, *args, **kwargs):
        if sides == 3:
            return Triangle(*args, **kwargs)
        else:
            return Square(*args, **kwargs)

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return (self.base * self.height) / 2

class Square:
    def __init__(self, length):
        self.length = length

    def area(self):
        return self.length*self.length

请注意,这只是一个示例。有多种方法可以获取解决方案,而无需借助上述的类工厂方法,即使我们确实选择以这种方式来实现该解决方案,为简洁起见也有一些注意事项(例如,明确声明元类) )

如果您要创建常规类(又称为非元类),那么__new__除非真正有特殊意义,例如ncoghlan答案中的可变与不可变方案(实际上是定义概念的更具体示例),否则这没有什么意义通过创建的类/类型的初始值/属性,__new__然后通过进行初始化__init__

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.