天天看點

類與對象

類的定義非常巧妙的運用了命名空間,要完全了解接下來的知識,需要先了解作用域和命名空間的工作原理。另外,這一切的知識對于任何進階 python 程式員都非常有用。

命名空間 是從命名到對象的映射。

目前命名空間主要是通過 python 字典實作的,不過通常不關心具體的實作方式(除非出于性能考慮),以後也有可能會改變其實作方式。

以下有一些命名空間的例子:内置命名(像 abs() 這樣的函數,以及内置異常名)集,子產品中的全局命名,函數調用中的局部命名。某種意義上講對象的屬性集也是一個命名空間。關于命名空間需要了解的一件很重要的事就是不同命名空間中的命名沒有任何聯系,例如兩個不同的子產品可能都會定義一個名為 maximize 的函數而不會發生混淆-使用者必須以子產品名為字首來引用它們。

稱 python 中任何一個“<code>.</code>”之後的命名為 屬性

例如,表達式 <code>z.real</code> 中的 <code>real</code> 是對象 <code>z</code> 的一個屬性。

嚴格來講,從子產品中引用命名是引用屬性:

表達式 <code>modname.funcname</code> 中,<code>modname</code> 是一個子產品對象,<code>funcname</code> 是它的一個屬性。

是以,子產品的屬性和子產品中的全局命名有直接的映射關系:它們共享同一命名空間!

屬性可以是隻讀過或寫的。後一種情況下,可以對屬性指派。你可以這樣: <code>modname.the_answer = 42</code>。可寫的屬性也可以用 <code>del 語句</code>删除。

例如: <code>del modname.the_answer</code> 會從 <code>modname 對象</code>中删除 <code>the_answer 屬性</code>。

不同的命名空間在不同的時刻建立,有不同的生存期。包含内置命名的命名空間在 python 解釋器啟動時建立,會一直保留,不被删除。子產品的全局命名空間在子產品定義被讀入時建立,通常,子產品命名空間也會一直儲存到解釋器退出。由解釋器在最高層調用執行的語句,不管它是從腳本檔案中讀入還是來自互動式輸入,都是 <code>__main__</code> 子產品的一部分,是以它們也擁有自己的命名空間(内置命名也同樣被包含在一個子產品中,它被稱作 <code>builtins</code> )。

當調用函數時,就會為它建立一個<code>局部命名空間</code>,并且在函數傳回或抛出一個并沒有在函數内部處理的異常時被删除。(實際上,用遺忘來形容到底發生了什麼更為貼切。)當然,每個遞歸調用都有自己的局部命名空間。

<code>作用域</code> 就是一個 python 程式可以直接通路命名空間的正文區域。這裡的直接通路意思是一個對名稱的錯誤引用會嘗試在命名空間内查找。盡管作用域是靜态定義,在使用時他們都是動态的。每次執行時,至少有三個命名空間可以直接通路的作用域嵌套在一起:

包含局部命名的使用域在最裡面,首先被搜尋;其次搜尋的是中層的作用域,這裡包含了同級的函數;

最後搜尋最外面的作用域,它包含内置命名。

首先搜尋最内層的作用域,它包含局部命名任意函數包含的作用域,是内層嵌套作用域搜尋起點,包含非局部,但是也非全局的命名

接下來的作用域包含目前子產品的全局命名

最外層的作用域(最後搜尋)是包含内置命名的命名空間

以下是一個示例,示範了如何引用不同作用域和命名空間,以及 global 和 nonlocal 如何影響變量綁定:

local 指派語句是無法改變 scope_test 的 spam 綁定。nonlocal 指派語句改變了 scope_test 的 spam 綁定,并且 global 指派語句從子產品級改變了 spam 綁定。

名詞

描述

類(class)

用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的執行個體。

類變量

類變量在整個執行個體化的對象中是公用的。類變量定義在類中且在函數體之外。類變量通常不作為執行個體變量使用。

資料成員

類變量或者執行個體變量用于處理類及其執行個體對象的相關的資料。

方法重寫

如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆寫(override),也稱為方法的重寫。

執行個體變量

定義在方法中的變量,隻作用于目前執行個體的類。

繼承

即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承也允許把一個派生類的對象作為一個基類對象對待。例如,有這樣一個設計:一個dog類型的對象派生自animal類,這是模拟"是一個(is-a)"關系(例圖,dog是一個animal)。

執行個體化

建立一個類的執行個體,類的具體對象。

方法

類中定義的函數。

對象

通過類定義的資料結構執行個體。對象包括兩個資料成員(類變量和執行個體變量)和方法(執行個體方法,類方法,靜态方法)

空行

函數之間或類的方法之間用空行分隔,表示一段新的代碼的開始。類和函數入口之間也用一行空行分隔,以突出函數入口的開始。

空行與代碼縮進不同,空行并不是python文法的一部分。書寫時不插入空行,python解釋器運作也不會出錯。但是空行的作用在于分隔兩段不同功能或含義的代碼,便于日後代碼的維護或重構。

記住:空行也是程式代碼的一部分。

類的格式:

資料屬性會覆寫同名的方法屬性。為了避免意外的名稱沖突,這在大型程式中是極難發現的 bug,使用一些約定來減少沖突的機會是明智的。

可能的約定包括:大寫方法名稱的首字母,使用一個唯一的小字元串(也許隻是一個下劃線)作為資料屬性名稱的字首,或者方法使用動詞而資料屬性使用名詞。

資料屬性可以被方法引用,也可以由一個對象的普通使用者(客戶)使用。換句話說,類不能用來實作純淨的資料類型。

事實上,python 中不可能強制隐藏資料——一切基于約定(如果需要,使用 c 編寫的 python 實作可以完全隐藏實作細節并控制對象的通路。這可以用來通過 c 語言擴充 python)。

客戶應該謹慎的使用資料屬性——客戶可能通過踐踏他們的資料屬性而使那些由方法維護的常量變得混亂。

注意:隻要能避免沖突,客戶可以向一個執行個體對象添加他們自己的資料屬性,而不會影響方法的正确性——再次強調,命名約定可以避免很多麻煩。

從方法内部引用資料屬性(或其他方法)并沒有快捷方式。這實際上增加了方法的可讀性:當浏覽一個方法時,在局部變量和執行個體變量之間不會出現令人費解的情況。

一般,方法的第一個參數被命名為 self。

這僅僅是一個約定:對 python 而言,名稱 <code>self</code> 絕對沒有任何特殊含義。(但是請注意:如果不遵循這個約定,對其他的 python 程式員而言你的代碼可讀性就會變差,而且有些 類檢視器 程式也可能是遵循此約定編寫的。)

類屬性的任何函數對象都為那個類的執行個體定義了一個方法。函數定義代碼不一定非得定義在類中:也可以将一個函數對象指派給類中的一個局部變量。

例如:

類可以看作是一種把對象分組歸類的方法。

舊式類的定義(即将被淘汰)

新式類與舊式類的差別主要表現在對超類命名空間檢索的使用算法不同。

舊式類可以直接省略括号和超類清單,而新式類以object作為python中所有類的預設超類。

類對象支援兩種操作:屬性引用和執行個體化。

屬性引用使用和 python 中所有的屬性引用一樣的标準文法:<code>obj.name</code>。

類對象建立後,類命名空間中所有的命名都是有效屬性名。

示例:

類執行個體化後,可以使用其屬性,實際上,建立一個類之後,可以通過類名通路其屬性。

很多類都傾向于将對象建立為有初始狀态的。是以類可能會定義一個名為 <code>__init__()</code> 的特殊方法(構造方法),像下面這樣:

類定義了 <code>__init__()</code> 方法的話,類的執行個體化操作會自動調用 <code>__init__()</code> 方法。是以在下例中,可以這樣建立一個新的執行個體:

當然,<code>__init__()</code> 方法可以有參數,參數通過 <code>__init__()</code> 傳遞到類的執行個體化操作上。例如:

在類地内部,使用 <code>def</code> 關鍵字來定義一個方法,與一般函數定義不同,類方法必須包含參數 self, 且為第一個參數,self 代表的是類的執行個體。

類的方法與普通的函數隻有一個特别的差別——它們必須有一個額外的第一個參數名稱, 按照慣例它的名稱是self。

從執行結果可以很明顯的看出,self 代表的是類的執行個體,代表目前對象的位址,而 self.class 則指向類。

self 不是 python 關鍵字,我們把他換成 runoob 也是可以正常執行的:

# 新式類

類的成員:

成員變量:

成員方法:

執行個體方法

類方法

靜态方法

property(修飾器)

以下均以例子說明:

python的私有并不是絕對的。

python解釋器對于類中所有加了雙下劃線的成員名會做一些名字上的替換,若存在一個成員的名字為<code>__fooname</code>,則解釋器會在檢測到它存在後将其替換為<code>_classname__fooname</code>,即在成員名前加上單下劃線開頭的類名。

類的私有屬性

<code>__private_attrs</code>:兩個下劃線開頭,聲明該屬性為私有,不能在類地外部被使用或直接通路。在類内部的方法中使用時 <code>self.__private_attrs</code>。

類的方法

在類地内部,使用 def 關鍵字來定義一個方法,與一般函數定義不同,類方法必須包含參數 self,且為第一個參數,self 代表的是類的執行個體。

self 的名字并不是規定死的,也可以使用 this,但是最好還是按照約定是用 self。

類的私有方法

<code>__private_method</code>:兩個下劃線開頭,聲明該方法為私有方法,隻能在類的内部調用 ,不能在類地外部調用。<code>self.__private_methods</code>。

類的專有方法:

<code>__init__</code> : 構造函數,在生成對象時調用

<code>__del__</code>: 析構函數,釋放對象時使用

<code>__repr__</code> : 列印,轉換

<code>__setitem__</code>: 按照索引指派

<code>__getitem__</code>: 按照索引擷取值

<code>__len__</code>: 獲得長度

<code>__cmp__</code>: 比較運算

<code>__call__</code>: 函數調用

<code>__add__</code>: 加運算

<code>__sub__</code>: 減運算

<code>__mul__</code>: 乘運算

<code>__div__</code>: 除運算

<code>__mod__</code>: 求餘運算

<code>__pow__</code>: 乘方

類變量和類方法可以被執行個體成員方法以隻讀的方式通路。

靜态方法位于類命名空間中的一種特殊函數,無法對任何執行個體成員進行操作。

可以通路類變量和類方法,但是,類的執行個體對象無法調用靜态方法。

目的:

修飾執行個體方法,以改變對外的通路方式,使得其在外部被通路時更像是在通路一個變量,而非函數。

從上述結果可以看出,bird類的名叫<code>name</code>的對象完全成為了<code>__name</code>的替身。有了<code>name</code>作為通路的中間層,一些驗證的邏輯就可以在<code>name</code>的方法中實作了。

<code>__init__</code>關鍵字是類的構造方法。

在python中,為一個類建立一個 執行個體對象 時,會立即調用類的構造方法(即當對象被建立後,python解釋器會去類中查找<code>__init__</code>函數)。

<code>__init__</code>函數為類添加一些初始化的屬性或功能。

類的繼承:

函數重寫

多重繼承

python 同樣支援類的繼承,如果一種語言不支援繼承,類就沒有什麼意義。派生類的定義如下所示:

需要注意圓括号中基類的順序,若是基類中有相同的方法名,而在子類使用時未指定,python從左至右搜尋 即方法在子類中未找到時,從左到右查找基類中是否包含方法。

baseclassname(示例中的基類名)必須與派生類定義在一個作用域内。除了類,還可以用表達式,基類定義在另一個子產品中時這一點非常有用:

<code>class derivedclassname(modname.baseclassname):</code>

如果你的父類方法的功能不能滿足你的需求,你可以在子類重寫你父類的方法,執行個體如下:

多态

動态類型:一個引用可以綁定任意類型的對象。

python同樣支援運算符重載,我麼可以對類的專有方法進行重載,執行個體如下:

探尋有趣之事!