Python(及Python C API):__new__与__init__的区别

154 投票
6 回答
38446 浏览
提问于 2025-04-16 10:59

我接下来要问的问题似乎和Python中__new__和__init__的用法重复,但无论如何,我还是不太明白__new____init__之间的实际区别是什么。

在你急着告诉我__new__是用来创建对象的,而__init__是用来初始化对象的之前,我想说:我明白这个道理。 其实,这种区分对我来说很自然,因为我有C++的经验,在C++中我们有placement new,它同样将对象的分配和初始化分开。

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

new成员负责创建(而不是初始化)该类型的对象。在Python中,它被称为__new__()方法。... 实现new方法的一个原因是确保实例变量的初始值

所以,是的,我明白__new__的作用,但尽管如此,我仍然不明白它在Python中有什么用。给出的例子说__new__可能有用,如果你想“确保实例变量的初始值”。那么,这不正是__init__要做的事情吗?

在C API教程中,展示了一个创建新类型(叫做“Noddy”)的例子,并定义了该类型的__new__函数。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__方法中做到这一点,不是吗?

6 个回答

45

可能还有其他地方可以用到 __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__ 来实现这一点——如果你试图在 __init__ 中修改 self,解释器会报错,告诉你不能修改一个不可变的对象。

47

__new__() 方法可以返回不同于它所绑定的类的对象。而 __init__() 方法只是用来初始化已经存在的类实例。

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

主要的区别在于可变类型和不可变类型。

__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__ 方法会更清晰。

撰写回答