假设我们有一个类 Foo:

1
2
3
4
class Foo(object):
    def __init__(self, x, y=0):
        self.x = x
        self.y = y

如果你实例化它(创建这个类的一个实例),底层到底发生了什么?

1
f = Foo(1, y=2)

当我们调用 Foo 的时候,到底什么函数或方法被调用了呢?大多数初学者,甚至许多有经验的Python 程序员会立刻回答 __init__ 方法被调用了。但是,如果你停下来细细想一想,这远不是一个正确的答案。

__init__ 方法本身不返回对象,但是调用 Foo(1, y=2) 却返回了对象。另外,__init__ 是期望有一个 self 参数的,但在调用 Foo(1, y=2) 的时候却不存在这样的参数。这里的工作有一点点复杂,因此在这篇文章中,我们将一起探究一下当你在 Python 中实例一个类时会发生什么。

创建顺序

实例化一个对象在 Python 中包括几个阶段,但 Python 美妙之处在于它们始终保持自身的 Pythonic - 理解这些步骤能够加深我们对 Python 的了解。Foo是一个类,但在 Python 中,类也是对象!类,函数,方法和实例的这些都是对象,当你在他们的名字后面加上括号的时候,其实你是调用了它们的方法 __call__。所以 Foo(1, y=2) 等价于 Foo.__call__(1, y=2)__call__ 方法是类 Foo 定义的,那么类 Foo 又是属于谁的?

1
2
>>> Foo.__class__
<class 'type'>

通过上面这段示例,我们可以发现 Foo 是 类型 type 的对象,然后调用 __call__ 方法会返回类 Foo 的对象。接下来,我们会看一下 type 中的方法 __call__ 是怎样的,这个方法很复杂,但是我们会尽可能简化它,下面,我们会贴出 CPython 中的 C代码以及 Pypy 中的 Python 实现,我发现阅读 Python 的源代码非常有趣,当你阅读源代码的时候,不要怕,像我下面一样简化它:

Cpython:

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *obj;

    if (type->tp_new == NULL) {
        PyErr_Format(PyExc_TypeError,
                     "cannot create '%.100s' instances",
                     type->tp_name);
        return NULL;
    }

    obj = type->tp_new(type, args, kwds);
    obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
    if (obj == NULL)
        return NULL;

    /* Ugly exception: when the call was type(something),
       don't call tp_init on the result. */
    if (type == &PyType_Type &&
        PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&
        (kwds == NULL ||
         (PyDict_Check(kwds) && PyDict_Size(kwds) == 0)))
        return obj;

    /* If the returned object is not an instance of type,
       it won't be initialized. */
    if (!PyType_IsSubtype(Py_TYPE(obj), type))
        return obj;

    type = Py_TYPE(obj);
    if (type->tp_init != NULL) {
        int res = type->tp_init(obj, args, kwds);
        if (res < 0) {
            assert(PyErr_Occurred());
            Py_DECREF(obj);
            obj = NULL;
        }
        else {
            assert(!PyErr_Occurred());
        }
    }
    return obj;
}

Pypy

 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
32
33
34
35
36
def descr_call(self, space, __args__):
    promote(self)
    # invoke the __new__ of the type
    if not we_are_jitted():
        # note that the annotator will figure out that self.w_new_function
        # can only be None if the newshortcut config option is not set
        w_newfunc = self.w_new_function
    else:
        # for the JIT it is better to take the slow path because normal lookup
        # is nicely optimized, but the self.w_new_function attribute is not
        # known to the JIT
        w_newfunc = None
    if w_newfunc is None:
        w_newtype, w_newdescr = self.lookup_where('__new__')
        if w_newdescr is None:    # see test_crash_mro_without_object_1
            raise oefmt(space.w_TypeError, "cannot create '%N' instances",
                        self)
        w_newfunc = space.get(w_newdescr, self)
        if (space.config.objspace.std.newshortcut and
            not we_are_jitted() and
            isinstance(w_newtype, W_TypeObject)):
            self.w_new_function = w_newfunc
    w_newobject = space.call_obj_args(w_newfunc, self, __args__)
    call_init = space.isinstance_w(w_newobject, self)

    # maybe invoke the __init__ of the type
    if (call_init and not (space.is_w(self, space.w_type) and
        not __args__.keywords and len(__args__.arguments_w) == 1)):
        w_descr = space.lookup(w_newobject, '__init__')
        if w_descr is not None:    # see test_crash_mro_without_object_2
            w_result = space.get_and_call_args(w_descr, w_newobject,
                                               __args__)
            if not space.is_w(w_result, space.w_None):
                raise oefmt(space.w_TypeError,
                            "__init__() should return None")
    return w_newobject

忽略掉错误检查,然后实例化一个类的代码等价于:

1
2
3
4
5
def __call__(obj_type, *args, **kwargs):
    obj = obj_type.__new__(*args, **kwargs)
    if obj is not None and issubclass(obj, obj_type):
        obj.__init__(*args, **kwargs)
    return obj

__new__ 为对象分配内存,然后将对象构造为一个空对象,然后再调用 __init__ 方法初始化它。

总结:

自定义

现在,我们把注意力放到到 __new__ 方法。事实上,它是实际创建对象的方法。因为 __new__ 方法比较复杂,我们就不详细的阅读具体实现代码了,但是,我们知道一个事实即可,那就是:它为对象分配空间并返回它。有趣的是,但你知道它做了什么之后,你可以用它来定制一些有趣的方式来创建实例。值得注意的是,虽然 __new__ 是一个静态方法,但是你不需要用 @staticmethod 来声明,因为它是 Python解释器 的特例。

一个利用 __new__ 方法的很强大的示例就是实现 Singleton 类:

1
2
3
4
5
6
7
class Singleton(object):
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls, *args, **kwargs)
        return cls._instance

然后尝试一下:

1
2
3
4
>>> s1 = Singleton()
... s2 = Singleton()
... s1 is s2
True

需要注意的是,在这个单例实现中,__init__方法在每次调用 Singleton() 的时候都会被调用的。

另一个类似的例子就是实现 Borg design pattern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Borg(object):
    _dict = None

    def __new__(cls, *args, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
        if cls._dict is None:
            cls._dict = obj.__dict__
        else:
            obj.__dict__ = cls._dict
        return obj

使用示例:

1
2
3
4
5
6
7
8
9
Then:

>>> b1 = Borg()
... b2 = Borg()
... b1 is b2
False
>>> b1.x = 8
... b2.x
8

一个非常重要的提示:这些例子只是说明一下 __new__ 的强大威力,但是只是说我们可以用它,并不意味着我们应该用他:

__new__ is one of the most easily abused features in Python. It’s obscure, riddled with pitfalls, and almost every use case I’ve found for it has been better served by another of Python’s many tools. However, when you do need __new__, it’s incredibly powerful and invaluable to understand.

– Arion Sprague, Python’s Hidden New

在 Python 中基本上不会有说使用 __new__ 是最合适的解决办法。我们最常见的误区就是,当你手上有把锤子的时候,你看什么都像钉子,但你知道 __new__ 这个方法后,你可能会觉得很多问题都可以通过 __new__ 来解决。这个时候,你需要谨记前辈们的经验:更好的设计胜过强大的新工具__new__方法并不永远是最好的。

Reference