Python(及Python C API):__new__与__init__的区别
我接下来要问的问题似乎和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 个回答
可能还有其他地方可以用到 __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
,解释器会报错,告诉你不能修改一个不可变的对象。
__new__()
方法可以返回不同于它所绑定的类的对象。而 __init__()
方法只是用来初始化已经存在的类实例。
>>> class C(object):
... def __new__(cls):
... return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5
主要的区别在于可变类型和不可变类型。
__new__
方法的第一个参数是一个 类型,通常会返回这个类型的新实例。所以它适用于可变类型和不可变类型。
__init__
方法的第一个参数是一个 实例,它会修改这个实例的属性。对于不可变类型来说,这样做是不合适的,因为这会允许在创建后通过调用 obj.__init__(*args)
来修改它们。
我们来比较一下 tuple
和 list
的行为:
>>> 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__
方法会更清晰。