天天看點

Python 進階_OOP 面向對象程式設計_執行個體屬性和方法目錄構造器和解構器執行個體方法執行個體屬性執行個體屬性和類屬性的差別

<a href="#%E7%9B%AE%E5%BD%95">目錄</a>

<a href="#%E6%9E%84%E9%80%A0%E5%99%A8%E5%92%8C%E8%A7%A3%E6%9E%84%E5%99%A8">構造器和解構器</a>

<a href="#%E6%9E%84%E9%80%A0%E5%99%A8-init">構造器 __init__</a>

<a href="#%E7%9C%9F%E6%9E%84%E9%80%A0%E5%99%A8-new">真構造器 __new__</a>

<a href="#%E8%A7%A3%E6%9E%84%E5%99%A8-del">解構器 __del__</a>

<a href="#%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95">執行個體方法</a>

<a href="#python-%E4%B8%AD%E7%9A%84-%E6%8A%BD%E8%B1%A1%E6%96%B9%E6%B3%95">Python 中的 抽象方法</a>

<a href="#%E5%AE%9E%E4%BE%8B%E5%B1%9E%E6%80%A7">執行個體屬性</a>

<a href="#%E6%9F%A5%E7%9C%8B%E5%AE%9E%E4%BE%8B%E5%B1%9E%E6%80%A7">檢視執行個體屬性</a>

<a href="#%E5%AE%9E%E4%BE%8B%E5%B1%9E%E6%80%A7%E5%92%8C%E7%B1%BB%E5%B1%9E%E6%80%A7%E7%9A%84%E5%8C%BA%E5%88%AB">執行個體屬性和類屬性的差別</a>

<a href="#%E8%AE%BF%E9%97%AE%E4%B8%8D%E5%8F%AF%E5%8F%98%E7%B1%BB%E5%B1%9E%E6%80%A7">通路不可變類屬性</a>

<a href="#%E8%AE%BF%E9%97%AE%E5%8F%AF%E5%8F%98%E7%B1%BB%E5%B1%9E%E6%80%A7">通路可變類屬性</a>

類函數 <code>__init__()</code> 是 Python 類中預定義的方法,需要被重載才會生效。以雙下劃線 “__” 開頭和結尾, 在 Python 中使用這種命名方式的方法會被了解為是一種特殊方法, Python 的特殊方法功能非常豐富, 種類也很多, 在聲明變量名的時候要注意不要和特殊方法重名.

通常,構造器用于在 執行個體化對象被建立後,傳回這個執行個體之前 的這段時間裡,執行一些特定的任務或設定。例如:初始化執行個體對象屬性(以 self 關鍵字調用的屬性)。它在執行個體化一個新的對象時被自動調用,是以除了初始化執行個體屬性之外,還常被用于運作一些初步的診斷代碼。其調用的具體步驟:

1. 建立類的執行個體化對象

2. Python 解析器檢查類是否實作了構造器

3. 若有,則執行構造器的實作,且要求建立對象的時候傳入對應的實參。執行個體對象會傳遞給第一個形參 self 。

4. 若沒有,則直接傳回執行個體對象

一般建議在構造器中設定需要初始化的執行個體屬性,而且構造器應該傳回 None,即沒有 return 語句。

與 <code>__init__()</code> 相比 <code>__new__()</code> 才是真正的構造器,實際上,在 Python 解析器中是先調用了 <code>__new__()</code> 生成一個執行個體,再将該執行個體對象傳入 <code>__init__()</code> 實作初始化操作。但 <code>__new__()</code> 很少需要我們去重載,一般隻有在派生了不可變類型的子類後需要重載,EG. 派生 String/Int/Tuple 等

為什麼說 <code>__new__()</code> 是真·構造器呢?

因為這個特殊的類方法是真的傳回了一個類的執行個體對象,而不像 <code>__init__()</code> 是傳入了一個執行個體化對象。

EXAMPLE:不可表類型的派生

類 RoundFloat 是類 float 的子類,我們通過重載父類的 <code>__new__()</code> 構造器來定制一個新的不可變類型(Python 2.2之後将類和類型統一了,是以可以繼承 Python 的内置資料類型)。當執行個體化 RoundFloat 的對象時,實際上是執行個體化了Python 内置資料類型 Float 的對象,并對這個對象做了一些定制化的操作(round(val, 2))。

NOTE:即便我們也可以通過重載 <code>__init__()</code> 來實作這個結果,但這裡卻不能這麼做。因為如果 <code>__new__()</code> 沒有被重載的話,仍會預設調用父類 Float 的構造器,建立 Float 類型的對象,而不是建立現在的 RoundFloat 類型對象。這也是兩者的本質差別。

解構器 <code>__del__()</code> 會在執行個體對象被垃圾回收之前被 Python 解析器調用一次(隻會調用一次,之後就回收執行個體對象)。解構器是執行個體對象釋放資源前提供特殊處理功能的方法。一般我們很少會用到,但解構器是檢查對象引用狀态的好方法,加入對象仍然存在引用時,解構器是不會執行的。

注意不要随便重載解構器。

執行個體方法的基本特征就是要傳入一個執行個體對象作為實參。

在 Python 看來,執行個體方法嚴格來說屬于類屬性的一種,執行個體方法隻能通過執行個體化對象來調用,無法通過類來直接調用。

如果類直接調用執行個體方法的話,會觸發 TypeError,因為執行個體方法需要綁定一個執行個體化對象,這就是 self 存在的意義。所有的含有 self 關鍵字的屬性和方法都隻能通過類的執行個體化對象來調用。是以,一般而言類中定義的執行個體方法都需要含有 self 形參,當然也可以通過别的方式來解除這種限制,這個我們後面會說到。

Python 并不支援 Java 中的抽象方法,但可以通過在父類中 <code>``raise NotImplememtedError</code> 來實作同樣的效果。抽象方法的意義在于可以強制的規範在子類中必需定義與父類中方法同名的子類成員方法。

是以通過這種方式,程式員們隻能乖乖在子類的重載這個同名方法了,這對程式的規範做了很好的限制。

在類定義中,通過 self 關鍵字來調用的屬性,即屬于執行個體對象的屬性,類對象是不能調用的。類屬性的建立實際上是通過調用執行個體方法 <code>__setattr__</code> 來完成的。<code>x.__setattr__('name', value) &lt;==&gt; x.name = value</code>,除此之外還有内建函數 setattr(),<code>setattr(object, name, value) &lt;==&gt; object.name = value</code>

Python 可以在任意的時間内建立一個命名空間,是以我們可以在建立了執行個體對象之後定義執行個體屬性,也可以在定義類的時候建立執行個體屬性。後者建立執行個體屬性最重要的方式就是構造器 <code>__init__()</code> 。

當然,我們除了可以在構造器中建立執行個體屬性之外,還可以在類體的任意地方建立,當我們并不推薦這麼做。

檢視執行個體屬性和檢視類屬性是一樣的,可以通過 <code>dir()</code> 和 <code>instance.__dict__</code> 來檢視。

EXAMPLE:

再與檢視類屬性做一個對比:

NOTE: 對于類或實力對象來說 <code>__dict__</code> 都是可以手動修改的,但是并不建議我們手動去修改這個屬性字典。

我們還能夠通過内置方法 getattr() 或執行個體方法 <code>__getattribute__()</code> 來檢視

類屬性與執行個體無關,類屬性的值不會因為執行個體化對象而改變,除非在執行個體對象中 顯式 的改變了 可變類屬性 的值。類屬型的值就像是靜态成員那樣被使用,我們一般采用 <code>class.attr</code> 的方式來調用類屬性 attr,除此我們也能夠使用執行個體對象來調用類屬性 <code>instance.attr</code>,但前提是執行個體對象中沒有同名的執行個體屬性,否則 <code>instance.attr</code> 中的 attr 就是一個執行個體屬性,傳回執行個體屬性的值。相反,執行個體屬性隻能由類執行個體對象來調用。

類屬性類似于一個靜态屬性,在定義類屬性的時候會在記憶體開辟靜态空間,用于存放。而且這個靜态空間是類的所有執行個體對象都可以通路的,是以類屬性是被所有的由該類執行個體化的對象共享的。

類屬性的改變會被執行個體對象感覺。

對于不可變類屬性(int/str/tuple)而言,執行個體對象是無法改變類屬性的值得。但如果類屬性是可變資料類型,那麼執行個體對象就可以顯式的修改其的值。

從上面的例子可以看出,執行個體對象可以引用類屬性(前提是執行個體對象沒有同名的執行個體屬性),卻不能改變類屬性,而是建立了一個新的執行個體屬性并覆寫了同名的類屬性。

執行個體對象可以修改類屬性的本質是,執行個體對象在沒有建立一個新的執行個體屬性的基礎上進行了修改。

注意:一般認為通過執行個體對象來修改類屬性是危險的,會有很多潛在的風險。是以建議使用類名來修改類屬性,而不是執行個體對象名。