我们知道,Python 的构造函数有两个,一个是我们最常用的 __init__ ,另一个是很少用到的 __new__。而从它们的函数定义 def __new__(cls, [...]) 和 def __init__(self, [...]) 可以知道,__init__ 被调用时实例已经被创建(就是 self 参数所引用的);而 __new__ 被调用的时候,实例不一定已被创建(暂时只能这么说),而 __new__ 的第一个参数为当前类的引用。

所以我们可以通过一些途径,跟踪 Python 对象的实例化过程,来研究 __new__ 和 __init__ 的工作。这个途径就是上一篇博文 利用 with 语法实现可拆卸的装饰器 中用到的函数调用轨迹装饰器。

先将上篇博文的代码保存为一个 Python 模块 withaop,py,然后在同目录下再创建一个文件 instantiation.py ,这个 instantiation.py 就是我们的工作区。导入 withaop 模块中的 trace 装饰器,然后就可以开始了。

先从最简单的类开始:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from withaop import trace

class MyClass(object):
    __new__ = trace(object.__new__) # 装饰 __new__ 方法

    @trace # 装饰 __init__ 方法
    def __init__(self):
        pass

# 实例化
my_instance = MyClass()

可以看到上面代码中,__new__ 方法和 __init__ 方法安装装饰器的方式是不同的。__new__ 方法可以直接将 object 的 __new__ 方法安装上装饰器,然后赋予 MyClass。这是因为所有没有自己定义 __new__ 的类(特指 new-style 类,忽视 old-style 类,下同),都会在被创建时继承 object 的 __new__ 引用。严格意义上来说,__new__ 甚至都不是一个方法,而是一个内建函数。这点在 Python Shell 一试便知:

而 __init__ 则不能照搬,因为 object 是没有 __init__ 成员的。但这并不意味着 object 的子类在没有用户定义 __init__ 时会没有 __init__ ,子类在被创建时如果没有用户定义 __init__ 则会被赋予一个空的 __init__ 方法,正如 def __init__(self): pass。所以,这里采取的方式是自行定义一个 __init__ 方法,并安装装饰器。

结果上述步骤,我们就模拟了一个没有任何定义的类 class MyClass(object): pass 并给 __new__ 和 __init__ 安装了轨迹跟踪装饰器。

运行,可以看到结果:

在 MyClass 实例化生成 MyClass 实例的时候,首先被调用的是 __new__,第一个参数 cls 如我们所料是 MyClass 。而 __new__ 的返回值,注意返回值,是 MyClass object,也就是我们所期望的 MyClass 实例,是所有成员方法的第一个参数 self 所指向的实例对象。至此,我们得知了,__new__ 调用的实质是由类创建对象的过程,而 __init__ 则是在 MyClass 实例被创建之后自动调用的。我们可以理解为 __new__ 的职责是创建一个实例,而 __init__ 则是按照用户定义的内容初始化新创建的实例。

了解了这一点,我们就可以开始下一步,跟踪有具体 __init__ 的类创建的过程。下面定义的是一个 Person 类,__init__ 有两个普通参数用户名、密码和一个关键字参数作为选项。

class Person(object):
    __new__ = trace(object.__new__)

    @trace
    def __init__(self, username, password, **kwargs):
        self.usr, self.pwd = username, password
        self.opt = kwargs

tonyseek = Person('tonyseek', 
                  'my_password', 
                  has_blog=True, 
                  has_notebook=False)

然后运行,观察装饰器跟踪的结果如下图:

可以看到,我们为 __init__ 设置的参数,很诡异的先被传给了 __new__ ,然后才被传给了 __init__。为什么会这样呢?我提出我的一个猜测:众所周知 Python 和其他很多语言不同之处,是没有 new 操作符。实例化一个对象,和调用一个函数一样,是对类进行 call 操作,如下:

tonyseek = Person('tonyseek', 
                  'my_password', 
                  has_blog=True, 
                  has_notebook=False)

我的猜测就是,我们 call 一个类的本质是 call 其 __new__ 方法,而 __new__ 方法的本质就是这个类的工厂函数。所以我们 call Person 的时候传入的参数是被 __new__ 接收的。object 等内置基类定义的 __new__ 方法返回实例之前,又把调用自身所用到的参数用来 call 对象的 __init__ 来进行初始化操作。

这个猜测是否属实不得而知,而且比较难从 Python 语言层证实,因为我们很难知道 object.__new__ 到底做了什么。但是我相信这个猜测是属实的,为此单独使用 __new__ 来做一些测试。

把上面的 Person 引用进来,然后换一种实例化方法:

tonyseek = object.__new__(Person, 
                          'tonyseek', 
                          'my_password', 
                          has_blog=True, 
                          has_notebook=False)

print tonyseek
print tonyseek.usr

运行结果如下图

访问属性 tonyseek.usr 抛 AttributeError 了,可见 __init__ 根本没被调用。但是 Person 对象确确实实是生产出来了。可见 __new__ 是工厂函数的说法不是个奇迹。

__init__ 被无视是不是 object 没有 __init__ 的缘故呢?我们换种方式测试:

tonyseek = Person.__new__(Person, 
                          'tonyseek', 
                          'my_password', 
                          has_blog=True, 
                          has_notebook=False)

print tonyseek
print tonyseek.usr

运行结果如图:

从轨迹跟踪来看,的确是 Person 的 __new__ 被调用了。但是 __init__ 还是被无视了。据此我得出结论:__new__ 自己并不会隐式接触 __init__ ,而是在使用 Python 的实例化语法—— call 一个类的时候,Python 做了两件事,第一件事是调用 __new__ 将未初始化的对象(我称为 raw instance)生产出来,第二件事是在 raw instance 上调用 __init__ 初始化。而调用两个构造函数的时候,都会将 call 到类上的参数分发进去。

了解了整个机理,我想应该改口了:按照其他语言的习惯,__init__ 这个“初始化者”才是真正的“构造方法”,而 __new__ 是“工厂函数”。

- EOF -

作者: TonySeek 发表于 2011-08-14 23:00 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架