Python高级特性:类构造与析构

简介

很多面向对象的语言都提供了new关键字,通过new可以创建类的实例。Python的方式更加简单,一旦定义了一个类,直接使用函数操作符,即可创建类的实例。本文主要结合一些实际的例子,介绍了Python类的构造,初始化和析构的原理。

类的构造与初始化

Python涉及类的构造与初始化,有两个重要的方法:__new__( )和__init__( )和方法。前者完成实例对象的创建,后者完成对创建的实例对象的初始化工作。为更好的理解相关概念,我们先来看一个具体的例子:

'''
Created on Mar 23, 2015

@author: jliu
'''

class MyClass(object):
    def __new__(cls, *args, **kwargs):
        print '__new__ called'
        return object.__new__(cls, *args, **kwargs) #default factory
    
    def __init__(self, name):
        print '__init__ called'
        self.name = name

if __name__ == '__main__':
instance = MyClass("Learning Python")
MyClass类继承了object基类,实现了__new__()和__init__()方法,程序运行结果如下:
__new__ called
__init__ called
通过上面的例子,对于__new__和__init__方法有个大致的印象。接下来,我们具体讲解相关的构造与初始化方法。

__new__()元构造方法

特殊方法__new__()是一个元构造程序,每当一个对象必须被factory类实例化时都会调用它,且__new__方法的调用在__init__之前。
与__init__()相比,__new__()方法更像一个真正的构造器,__new__方法的调用需要将类cls作为它的第一个调用参数,它的责任是返回一个类的新的实例,因此,它可以在对象创建之前或之后修改类的实例,从而确保实例被设置为一个希望的状态。
这里我们可以将此与__init__方法做个比较:__init__调用时需要要将类的实例作为第一个参数,并且它并不返回任何东西,它的职责就是初始化这个实例。有些情况下,创建一个实例并不需要调用__init__,但没有办法在不调用__new__时创建一个实例。
__init__在子类中不会被隐式调用(子类初始化时,必须显式的调用父类的__init__方法),所以__new__可以用来确定在整个类层次中完成初始化工作。它使得可以在比__init__更低层次上定义一个初始化,这个初始化总是被调用。__new__()和__init__()在类创建时,都传入了(相同)参数。
我们在这里总结了一下__new__()方法的一些规则:
1. __new__是一个静态方法
2. __new__第一个参数必须是类,其它参数可以被构造器调用引用
3. 子类__new__方法覆盖了基类的__new__方法,可以在子类的__new__方法中调用基类的__new__方法。基类的__new__方法的第一个参数必须是传递给子类__new__方法的类参数,而不是基类参数。如果传递的是基类,你将得到一个基类的实例。
4. 子类__new__方法必须调用基类的__new__方法,这是创建对象实例的唯一方法。子类__new__方法通过做两件事情影响对象实例:给基类的__new__方法传递不同的参数;在实例创建之后修改实例对象(例如修改必要的实例参数)。
5. __new__方法必须返回一个实例对象。尽管通常要求该方法返回的新对象是它传入类参数的一个实例,但并没有要求必须这么做。如果你返回一个已存在的对象,构造器仍会调用它的__init__方法。如果你返回了不同类的一个实例,它会调用自己的__init__方法。如果你忘记返回,Python回返回None。
6. 对于不可变类,如int, str,tuple等,子类的__new__方法可返回已存在对象的引用,这是为什么__init__方法不需要做任何事情的原因:缓存的对象会被一边又一边的初始化(另外一个原因是__new__返回了一个被完全初始化的对象实例,__init__方法不需要做任何初始化了)。
7. 当你子类化一个内置不可变类型并且希望加入一些可变状态,最好是在__init__方法中初始化这些可变状态,而非在__new__方法中。
8. 如果你想变更构造器的参数,你通常不得不同时复写__new__和__init__方法来接受新的参数。然而,大部分内置类型会忽略方法不使用的参数,特别是不可变类(int, long, float, complex, str, unicode,tuple)有一个哑的__init__方法,而可变类(dict, list, file, super, classmethod, staticmethod, property)有一个哑的__new__方法。内置类型object类(所有其它类的基类)的__new__和__init__方法都是哑的。

__init__()实例初始化方法

当类被调用时,实例化的第一步是创建实例对象。一旦实例对象创建了,Python检查是否实现了__init__()方法。默认情况下,如果没有定义__init__(),对实例不会施加任何特殊操作,任何所需的特定操作,都需要程序实现__init__(),覆盖其默认行为。如果__init__()没有实现,则返回它的对象,实例化过程完毕。
然而,如果__init__()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递进去,像标准方法调用一样。调用类时,传进的任何参数都交给了__init__()。实际中,你可以想像成这样:把创建实例的调用当成是对构造器的调用。
总之,(a)你没有通过调用new 来创建实例,你也没有定义一个构造器。则Python 为你创建了对象; (b) __init__(),是在解释器为你创建一个实例后调用的第一个方法,在你开始使用它之前,这一步可以让你做些准备工作。

析构器方法

与构造器对应的,有一个特殊的析构器(destructor)方法名为__del__()。然而,由于Python 具有垃圾对象回收机制(靠引用计数),这个函数要直到该实例对象所有的引用都被清除掉后才会执行。Python 中的析构器是在实例释放前提供特殊处理功能的方法,它们通常没有被实现,因为实例很少被显式释放。
'''
Created on 2015年3月23日

@author: bob
'''

from sys import getrefcount
from gc import get_referrers
        
class MyClass(object):
    def __init__(self):
        print("MyClass init")
        
    def __del__(self):
        print("MyClass del")

if __name__ == '__main__':
    obj1 = MyClass()
    print("only one instance, refcount=%d" % getrefcount(obj1))
    obj3 = obj2 = obj1

    print("print the reference to obj1")
    print(get_referrers(obj1))
    print("now we have three instances now, refcount=%d" % getrefcount(obj1))
    del(obj2)
    print("After deleting the obj2, refcount=%d" % getrefcount(obj1))
    del(obj3)
    print("After deleting the obj3, refcount=%d" % getrefcount(obj1)) 
    del(obj1) 
    print("delete all instances")
运行结果
MyClass init
only one instance, refcount=2
print the reference to obj1
[{'__doc__': '\nCreated on 2015年3月23日\n\n@author: bob\n', '__loader__': <_frozen_importlib.SourceFileLoader object at 0x0000000000461710>, '__file__': 'E:\\workspace\\pythonstudy\\classtest\\testdel.py', '__package__': None, '__spec__': None, 'MyClass': , '__cached__': None, 'get_referrers': , 'obj3': <__main__.MyClass object at 0x0000000000461748>, '__builtins__': , 'obj2': <__main__.MyClass object at 0x0000000000461748>, '__name__': '__main__', 'getrefcount': , 'obj1': <__main__.MyClass object at 0x0000000000461748>}]
now we have three instances now, refcount=4
After deleting the obj2, refcount=3
After deleting the obj3, refcount=2
MyClass del
delete all instances
总结以下__del__的使用:
1. 调用 del x 不表示调用了x.__del__() -----前面也看到,它仅仅是减少x 的引用计数。
2. 如果你有一个循环引用或其它的原因,让一个实例的引用逗留不去,该对象的__del__()可能永远不会被执行。
3. __del__()未捕获的异常会被忽略掉(因为一些在__del__()用到的变量或许已经被删除了)。不要在__del__()中干与实例没任何关系的事情。
4. 除非你知道你正在干什么,否则不要去实现__del__()。
5. 如果你定义了__del__,并且实例是某个循环的一部分,垃圾回收器将不会终止这个循环——你需要自已显式调用del。
6. 不要忘记首先调用父类的__del__()。

参考资料

1.  https://www.python.org/download/releases/2.2/descrintro/#__new__
2.  http://stackoverflow.com/questions/510406/is-there-a-way-to-get-the-current-ref-count-of-an-object-in-python
3. Python核心编程

你可能感兴趣的:(Python)