Python类的构造,初始化和析构的原理

时间:2018-02-03 10:28:34   来源:Think-througher   阅读:

一、简介

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


 

二、类的构造与初始化

Python涉及类的构造与初始化,有两个重要的方法:__new__( )和__init__( )和方法。前者完成实例对象的创建,后者完成对创建的实例对象的初始化工作。为更好的理解相关概念,我们先来看一个具体的例子:
 
[python] view plain copy
  1. '''''
  2.  
  3. @author: 上海尚学堂 shsxt.com
  4. '''  
  5.   
  6. class MyClass(object):  
  7.     def __new__(cls, *args, **kwargs):  
  8.         print '__new__ called'  
  9.         return object.__new__(cls, *args, **kwargs) #default factory  
  10.       
  11.     def __init__(self, name):  
  12.         print '__init__ called'  
  13.         self.name = name  
  14.   
  15. if __name__ == '__main__':  
  16. instance = MyClass("Learning Python")  
MyClass类继承了object基类,实现了__new__()和__init__()方法,程序运行结果如下:
[plain] view plain copy
  1. __new__ called  
  2. __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 中的析构器是在实例释放前提供特殊处理功能的方法,它们通常没有被实现,因为实例很少被显式释放。
[python] view plain copy
  1. ''''
  2.  
  3. @author: 上海尚学堂 shsxt.com
  4. '''  
  5.   
  6. from sys import getrefcount  
  7. from gc import get_referrers  
  8.           
  9. class MyClass(object):  
  10.     def __init__(self):  
  11.         print("MyClass init")  
  12.           
  13.     def __del__(self):  
  14.         print("MyClass del")  
  15.   
  16. if __name__ == '__main__':  
  17.     obj1 = MyClass()  
  18.     print("only one instance, refcount=%d" % getrefcount(obj1))  
  19.     obj3 = obj2 = obj1  
  20.   
  21.     print("print the reference to obj1")  
  22.     print(get_referrers(obj1))  
  23.     print("now we have three instances now, refcount=%d" % getrefcount(obj1))  
  24.     del(obj2)  
  25.     print("After deleting the obj2, refcount=%d" % getrefcount(obj1))  
  26.     del(obj3)  
  27.     print("After deleting the obj3, refcount=%d" % getrefcount(obj1))   
  28.     del(obj1)   
  29.     print("delete all instances")  
运行结果
[plain] view plain copy
  1. MyClass init  
  2. only one instance, refcount=2  
  3. print the reference to obj1  
  4. [{'__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': <class '__main__.MyClass'>, '__cached__': None, 'get_referrers': <built-in function get_referrers>, 'obj3': <__main__.MyClass object at 0x0000000000461748>, '__builtins__': <module 'builtins' (built-in)>, 'obj2': <__main__.MyClass object at 0x0000000000461748>, '__name__': '__main__', 'getrefcount': <built-in function getrefcount>, 'obj1': <__main__.MyClass object at 0x0000000000461748>}]  
  5. now we have three instances now, refcount=4  
  6. After deleting the obj2, refcount=3  
  7. After deleting the obj3, refcount=2  
  8. MyClass del  
  9. delete all instances  
六、总结以下__del__的使用:
 
1. 调用 del x 不表示调用了x.__del__() -----前面也看到,它仅仅是减少x 的引用计数。
2. 如果你有一个循环引用或其它的原因,让一个实例的引用逗留不去,该对象的__del__()可能永远不会被执行。
3. __del__()未捕获的异常会被忽略掉(因为一些在__del__()用到的变量或许已经被删除了)。不要在__del__()中干与实例没任何关系的事情。
4. 除非你知道你正在干什么,否则不要去实现__del__()。
5. 如果你定义了__del__,并且实例是某个循环的一部分,垃圾回收器将不会终止这个循环——你需要自已显式调用del。
6. 不要忘记首先调用父类的__del__()。

上海尚学堂人工智能python培训感谢原文作者Think-througher的分享,原文地址:http://blog.csdn.net/jinguangliu/article/details/44498189
分享:0

电话咨询

客服热线服务时间

周一至周五 9:00-21:00

周六至周日 9:00-18:00

咨询电话

021-67690939
15201841284

微信扫一扫