论坛首页 编程语言技术论坛

Python 对象的实例化过程

浏览 8511 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-08-13   最后修改:2011-08-13

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

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

先将跟踪工具保存为一个 Python 模块 withaop.py:

 

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

import functools
import types
import hashlib
import time

def trace(func):
    """轨迹装饰器, 装饰目标函数"""
    @functools.wraps(func)
    def tracing_wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print("== %s : %s has been called" % (time.ctime(), func))
        for arg in args:
            print "== == argument: %s" % repr(arg)
        for argk,argv in kwargs.items():
            print "== == key-word argument: %s = %s" % (repr(argk),repr(argv))
        print "== == return: %s" % repr(result)
        return result
    return tracing_wrapper
 

然后在同目录下再创建一个文件 instantiation.py ,这个 instantiation.py 就是我们的工作区。导入 withaop 模块中的 trace 装饰器,然后就可以开始了。

先从最简单的类开始:

# -*- 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 -

论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics