Python(和Python C API):__ new__与__init__

Python (and Python C API): __new__ versus __init__

我将要问的问题似乎是python使用"new"和"init"的复制品。但不管怎样,我仍然不清楚__new____init__之间的实际区别。

在你急着告诉我__new__用于创建对象,__init__用于初始化对象之前,让我明确一点:我明白了。事实上,这种区别对我来说是很自然的,因为我有C++的经验,我们有新的布局,这类似地将对象分配与初始化分离开来。

python c api教程解释如下:

The new member is responsible for
creating (as opposed to initializing)
objects of the type. It is exposed in
Python as the __new__() method. ...
One reason to implement a new method is to assure the initial values of
instance variables.

所以,是的-我知道__new__的功能,但是尽管如此,我仍然不明白为什么它在Python中有用。给出的例子表明,如果您想"确保实例变量的初始值",那么__new__可能很有用。好吧,这不正是__init__所要做的吗?

在C API教程中,将显示一个示例,其中创建了一个新类型(称为"noddy"),并定义了该类型的__new__函数。noddy类型包含名为first的字符串成员,该字符串成员初始化为空字符串,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
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,它只是将所有实例变量成员初始化为空。因此,__new__方法的唯一好处是实例变量将以空字符串开始,而不是以空字符串开始。但是为什么这会有用呢,因为如果我们关心确保实例变量初始化为某个默认值,那么我们就可以在__init__方法中这样做?


差异主要出现在可变类型和不可变类型之间。

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

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

比较tuplelist的行为:

1
2
3
4
5
6
7
8
9
10
11
12
>>> 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__方法更容易编写,以及上述可变与不可变的区别之外,还可以利用分离,通过在__new__中设置任何绝对必需的实例不变量,使子类中调用父类__init__成为可选的。不过,这通常是一种可疑的做法——必要时,只调用父类__init__方法通常更清楚。


__new__可能还有其他用途,但有一个非常明显的用途:不使用__new__就不能对不可变类型进行子类化。例如,假设您想要创建一个tuple的子类,它只能包含0和size之间的整数值。

1
2
3
4
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__()只初始化类的现有实例。

1
2
3
4
5
6
7
8
9
>>> class C(object):
...   def __new__(cls):
...     return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5


不是一个完整的答案,但可能是一些能说明差异的东西。

当必须创建对象时,将始终调用__new__。在某些情况下,__init__无法被调用。一个例子是,当您从pickle文件中取消拾取对象时,它们将被分配(__new__,但不会初始化(__init__)。


只是想增加一个词来说明定义__new____init__的意图(与行为相反)。

当我试图理解定义一个类工厂的最佳方法时,我(和其他人)遇到了这个问题。我认识到,__new____init__在概念上不同的一种方式是,__new__的好处正是问题中所述的:

So the only benefit of the __new__ method is that the instance variable will start out as an empty string, as opposed to NULL. But why is this ever useful, since if we cared about making sure our instance variables are initialized to some default value, we could have just done that in the __init__ method?

考虑到所述的场景,当实例实际上是类本身时,我们关心实例变量的初始值。因此,如果我们在运行时动态地创建一个类对象,并且我们需要定义/控制关于正在创建的这个类的后续实例的特殊内容,那么我们将在一个元类的__new__方法中定义这些条件/属性。

我对此感到困惑,直到我真正考虑到这个概念的应用,而不仅仅是它的意义。下面是一个很有希望使区别变得清晰的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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的答案答案中的可变与不可变方案(本质上是定义通过EDOCX1创建的类/类型的初始值/属性的概念的更具体示例〔2〕然后通过__init__初始化。