1. 程式人生 > >Python高階特性:類構造與析構

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\[email protected]: 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>}]
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__()。

參考資料

3. Python核心程式設計

相關推薦

Python高階特性構造

簡介 很多面向物件的語言都提供了new關鍵字,通過new可以建立類的例項。Python的方式更加簡單,一旦定義了一個類,直接使用函式操作符,即可建立類的例項。本文主要結合一些實際的例子,介紹了Python類的構造,初始化和析構的原理。 類的構造與初始化 Python涉及類的

Vector模板----構造

      /* 基於C++平臺*/   typedef int rank; //用int來定義 “秩” 這種概念 #define DEFAULT_CAPACIITY 3 //預設初始容量,實際應用中可以取更大的值 template <type

關於繼承的構造調用分析

fff 調用父類 派生類的構造函數 臨時 臨時對象 構造函數 back 基類 原因分析   總體結論:派生類的構造函數在創建對象時調用,調用順序如下:        1.調用虛基類的構造函數(若有多個虛基類,調用順序為繼承的順序。);        2.調用基類的構造函

Python高階特性——列表生成式生成器

列表生成式:   1)、    L=[x*x for x in range(1,11) if x%2==0]  等價於    L = []    for x in range(1, 11):       if x%2==0:         L.append(x * x)  2)、    a=[m

python高階特性之迭代迭代器

全部測試程式碼 #! /usr/bin/env python3 #_*_ conding:utf-8 _*_ 迭代:Iterable #python中使用for ... in ...來迭代物件 #python的for迴圈抽象程度高,不僅可作用在list和tuple上,還可以在任何可

繼承-構造的順序

#include <iostream> //在繼承體系下: //建立物件時:先調父類構造,再調子類構造 //銷燬物件時:先調子類析構,再調父類析構 class A { int* p; public: A() { p = new int[10]; std::cout<

python裡的魔法方法1(構造

魔法方法——構造與析構 1、python程式設計的魔法方法: (1)魔法方法總是被雙下劃線包圍,例如__init__; (2)魔法方法是面向物件的python的一切。 2、__new__(class[,…])魔法方法 主要用來重新修改和對於實際類()裡面所帶引數的修改,__new__ 方法主要任務是

python高階特性切片/迭代/列表生成式/生成器

廖雪峰老師的教程上學來的,地址:python高階特性  下面以幾個具體示例演示用法: 一、切片 1.1 利用切片實現trim def trim(s): while s[:1] == " " or s[-1:] == " ": # 若第1個元素或最後1個元素為空格 if

包含物件成員的構造順序

首先,我們來看一段程式碼: #include<iostream> using namespace std; class A { public: A() { cout << "A's constructor." << endl;

Python學習筆記】python高階特性列表生成式

【根據廖雪峰python教程整理】 一、列表生成式 列表生成式即List Comprehensions,是Python內建的非常簡單卻強大的可以用來建立list的生成式。 舉個例子,要生成lis

面向對象中的繼承、封裝、構造函數

再次 釋放內存 創建 訪問 完整性 struct 事務 完整 參數 構造函數:是一種特殊的方法。主要用來在創建對象時初始化對象,即為對象成員變量賦值初始值,總與new運算符一起使用在創建對象的 語句中。兩根下劃線開頭,construct 作用:為

C++繼承中的構造

pause cout default sys class bject iostream pub efault #include <iostream> #include <string> using namespace std; class O

Swift 構造

前言 pri logs 允許 arr 管理 必須 直接 生命周期 前言 與 OC 一樣,Swift 中也存在構造和析構過程。不同的是,OC 中的構造方法和析構方法只是普通的方法,而 Swift 中構造器和析構器是一種特殊的結構。 1、構造器 在 Swift 中,類或者

DLL中物件的構造

動態連結庫中全域性變數的構造與析構: 背景:你在當前main函式中通過LoadLibrary來載入MFCLibraryDll.dll 1 MFCLibraryDll中的全域性變數A b的建立與析構會自動執行: 1)LoadLibrary執行時第一時間建立LoadLibrary中的全域

SGISTL原始碼閱讀四 物件的構造

SGISTL原始碼閱讀四 物件的構造與析構 前言 前面我們提到,SGISTL將空間配置和物件的構造分開操作了,前面的文章我們對空間配置已經做了描述,下面我們來看一下如何構造和析構物件。 深入原始碼 construc //接受一個指標和一個初值 template <c

C++筆記 第四十六課 繼承中的構造---狄泰學院

如果在閱讀過程中發現有錯誤,望評論指正,希望大家一起學習,一起進步。 學習C++編譯環境:Linux 第四十六課 繼承中的構造與析構 1.思考 如何初始化父類成員?父類建構函式和子類建構函式有什麼關係? 2.子類物件的構造 子類中可以定義建構函式 子類建構函式 必須對

[Nim] object的構造

在現代的程式語言當中,在設計時幾乎都拋棄了建構函式與解構函式,比如像Go、Rust等。但在實際開發中我們確實又需要這個特性,今天學習一下如何曲線救國。 我們先看看new的三使用方法 第一種:通過型別構造 type Person = object

魔方方法篇第一章--------構造

我們最為熟知的基本的魔法方法就是 __init__ ,我們可以用它來指明一個物件初始化的行為。然而,當我們呼叫 x = SomeClass() 的時候, __init__ 並不是第一個被呼叫的方法。事實上,第一個被呼叫的是 __new__ ,這個 方法才真正地建立了例項。當這個物件的生命週期結束的時候(指類被

繼承中的構造

                                      子類物件的構造 子類建構函式:必須對繼承而來的成員進行初始化----直接通過初始化列表或者賦值的方式進行初始                                            

C++——glibc全域性構造

_start -> __libc_start_main -> __libc_csu_init -> _init: Disassembly of section .init: 80480f4 <_init>: 80480f4: 55 push %ebp 80480f5: 89 e5