Python學習入門(26)—類(之一)

(本號正在連續推出以Python官網文檔為主線的系統學習Python的系列文章或視頻,感興趣的朋友們歡迎搜索關注。在這裡學習Python事半功倍!本文及後續文章如無特別聲明均以Windows平臺作為演示平臺,Python版本為:3.8.1)


類提供了一種將數據和功能捆綁在一起的方法。創建新類將創建新的對象類型,從而允許創建該類型的新實例。每個類實例都可以添加屬性來維護其狀態。類實例還可以擁有用於修改其狀態的方法(由類定義)。

與其他編程語言相比,Python的類機制添加的新語法和語義是最少的,它混合了C++和Modula-3中的類機制。Python類提供了面向對象編程的所有標準特性:類繼承機制允許多個基類,派生類可以覆蓋其基類的任何方法,而方法可以調用具有相同名稱的基類的方法。對象可以包含任意數量和類型的數據。與模塊一樣,類也具有Python的動態特性:它們在運行時創建,在創建之後可以進一步修改。

按C++術語的說法,通常類成員(包括數據成員)是公共的(除了下面的私有變量),所有成員函數都是虛函數。與在Modula-3中一樣,沒有用於從對象的方法中引用對象成員的簡化方法:方法函數聲明時使用顯式的第一個參數來表示其所屬對象,此參數在調用該方法時隱式提供。與Smalltalk一樣,類本身也是對象。這為導入和重命名提供了語義支持。與C++和Modula-3不同,內置類型可以用作用戶擴展的基類。與C++中一樣,大多數具有特殊語法的內置操作符(算術操作符、下標等)都可以在類中被重新定義。

對象和對象的名稱

Python的對象也是具有個體性的。可以將多個名稱(在多個範圍內)綁定到同一個對象,這些名稱在其他語言中稱為別名。在初次接觸Python時,這一點往往會被忽視。在處理不可變的基本類型(數字、字符串、元組)時,忽略了這一點不會有問題。然而對於可變對象,如列表、字典和大多數其他類型,忽視了這一點可能會造成問題。在程序中使用別名是有好處的,由於別名表現得像C++中的指針,這樣傳遞對象的操作是廉價的,因為本質上只傳遞一個指針。還有個好處是:如果函數修改了作為參數傳遞的對象,調用者將也能看到這一修改。

Python的作用域和命名空間

在介紹類之前,首先介紹一些關於Python作用域規則的內容。類定義對命名空間進行了一些巧妙的處理,完全理解這些需要了解作用域和命名空間是如何工作的。順便說一下,這部分知識對任何高級Python程序員都是有用的。先介紹幾個定義:

命名空間

是從名稱到對象的映射。目前,大多數命名空間都是以Python字典的方式實現的,怎麼實現並不重要(除了性能之外),實現方法將來是可能發生變化的。名稱空間的例子有對下面名字的映射:內置的名稱(包含abs()等函數和內置的異常名稱);模塊中的全局名;以及函數調用中的本地名稱。在某種意義上,對象的屬性集也形成命名空間。關於命名空間,需要注意的重要一點是,不同命名空間中的名稱之間沒有任何關係。例如,兩個不同的模塊都可以定義一個名為maximize的函數而不會混淆,但使用該函數的用戶必須在它前面加上模塊名,形如:module.maximize。

屬性 對點後面的任何名稱都稱作屬性。例如,表達式z.real是對象z的一個屬性。嚴格來說,對模塊中名稱的引用是屬性引用:在表達式modname.funcname中,modname是一個模塊對象,而funcname是它的一個屬性。在這種情況下,模塊的屬性和模塊中定義的全局名稱之間正好有一個簡單的映射:它們共享相同的命名空間。屬性可以是隻讀的,也可以是可寫的。在後一種情況下,可以對屬性賦值,例如modname.the_answer = 42。也可以用del語句刪除模塊屬性,例如del modname.the_answer,執行之後屬性the_answer將從modname對象中刪除。

命名空間可以在不同的時刻創建,並且具有不同的生存期。包含內置名稱的命名空間是在Python解釋器啟動時創建的,並且不會被刪除。模塊的全局命名空間是在模塊定義被讀入時創建的,通常模塊命名空間也會持續到解釋器退出為止。解釋器的頂層調用所執行的語句,無論是從腳本文件中讀取的,還是交互執行的,都被認為是一個名為_main__的模塊的一部分,因此它們有自己的全局命名空間。(內置的名稱實際上存在於一個名為builtins的模塊中。)

函數的本地命名空間在調用該函數時創建,在函數返回或引發未在函數中處理的異常時刪除。當然,每個遞歸調用都有自己的本地命名空間。

作用域 是Python程序的文本區域,在這裡可以直接訪問命名空間。這裡的“直接訪問”意味著對名稱的非限定引用會嘗試在命名空間中查找。雖然作用域是靜態確定的,但它們是動態使用的。在執行過程中的任何時候,至少有三個嵌套的作用域的命名空間是可以被直接訪問的:

  • 最內層的作用域(首先搜索它)包含本地名稱
  • 任何封閉函數的作用域(從最近的封閉作用域開始搜索)包含非本地的名稱,但也包含非全局的名稱
  • 向外倒數第二個作用域包含當前模塊的全局名稱
  • 最外層的作用域(最後搜索)是包含內置名稱

如果一個名稱被聲明為全局的,那麼所有引用和賦值都直接指向包含其所在模塊的全局名稱的中間作用域。要更新在最內層作用域之外的變量,可以使用nonlocal語句。如果沒有聲明為nonlocal 變量,那麼這些變量是隻讀的(對這樣一個變量進行寫操作,只會在最內部的作用域中創建一個新的局部變量,而相同名稱的外部變量則保持不變)。

通常,位於函數內部的局部作用域引用當前函數的局部名稱。在函數外部,局部作用域引用與全局作用域相同的命名空間,即所屬模塊的命名空間。類的定義會在局部作用域中引入一個命名空間。

一定要注意作用域是根據程序源文本確定的:在模塊中定義的函數的全局作用域引用的是該模塊的命名空間,不管從何處調用該函數,也不管調用的別名是什麼。另一方面,實際的名稱搜索是在運行時動態完成的,但是語言定義確正朝著靜態名稱解析的方向發展(在“編譯”時),所以不要依賴於動態名稱解析。(事實上,局部變量已經被靜態地確定了。)

Python的一個特殊之處是,如果沒有全局或非局部語句在起作用,則對名稱的賦值總是進入最內層的作用域。賦值不復制數據,它們只是將名稱綁定到對象。刪除也是如此,del x語句從局部作用域引用的名稱空間中移除x的綁定。實際上,所有引入新名稱的操作都使用局部作用域,特別是import語句和函數定義將模塊或函數名綁定到局部作用域中。

global語句可以用來指示某個變量位於全局範圍內,應該在全局範圍內進行綁定。nonlocal語句表示某個變量位於一個封閉的範圍內,應該在那裡進行綁定。

作用域和命名空間的示例如下:

<code>def scope_test():
def do_local():
spam = "local spam"

def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"

def do_global():
global spam
spam = "global spam"

spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)/<code>

輸出結果為:

<code>After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam/<code>

注意:在示例中可以看到,本地賦值(默認值)沒有改變scope_test對spam的綁定,nonlocal賦值更改了scope_test對spam的綁定,global賦值更改了模塊級綁定。還可以看到,在global賦值之前沒有針對spam的綁定。


[關於"類"部分的內容本篇未完,下篇將繼續講解]

【結束】


分享到:


相關文章: