?? 9.html
字號:
<HTML><!-- Mirrored from www.math.pku.edu.cn/teachers/lidf/docs/Python/9.html by HTTrack Website Copier/3.x [XR&CO'2005], Fri, 08 Jul 2005 11:49:15 GMT --><HEAD><TITLE>第九章 類</TITLE></HEAD><BODY LINK="#0000ff"><H1>第九章 類</H1><P>Python是一個真正面向對象的語言,它只增加了很少的新語法就實現了類。它的類機制是C++和Modula-3的類機制的混合。Python的類并不嚴格限制用戶對定義的修改,它依賴于用戶自覺不去修改定義。然而Python對類最重要的功能都保持了完全的威力。類繼承機制允許多個基類的繼承,導出類可以重載基類的任何方法,方法可以調用基類的同名方法。對象可以包含任意多的私有數據。</P><P>用C++術語說,所有類成員(包括數據成員)是公用的,所有成員函數是虛擬(virtual)的。沒有特別的構建函數或銷毀函數(destructor)。如同在Modula-3中一樣,從對象的方法中要引用對象成員沒有簡捷的辦法:方法函數的必須以對象作為第一個參數,而在調用時則自動提供。象在Smalltalk中一樣,類本身也是對象,實際上這里對象的含義比較寬:在Python中所有的數據類型都是對象。象在C++或Modula-3中一樣,內置類型不能作為基類由用戶進行擴展。并且,象C++但不象Modula-3,多數有特殊語法的內置函數(如算術算符、下標等)可以作為類成員重定義。</P><H2>9.1 關于術語</H2><P>Python的對象概念比較廣泛,對象不一定非得是類的實例,因為如同C++和Modula-3而不同于Smalltalk,Python的數據類型不都是類,比如基本內置類型整數、列表等不是類,甚至較古怪的類型如文件也不是類。然而,Python所有的數據類型都或多或少地帶有一些類似對象的語法。</P><P>對象是有單獨身份的,同一對象可以有多個名字與其聯系,這在其他語言中叫做別名。這樣做的好處乍一看并不明顯,而且對于非可變類型(數字、字符串、序表(tuple))等沒有什么差別。但是別名句法對于包含可變對象如列表、字典及涉及程序外部物件如文件、窗口的程序有影響,這可以有利于程序編制,因為別名有些類似指針:比如,傳遞一個對象變得容易,因為這只是傳遞了一個指針;如果一個函數修改了作為參數傳遞來的對象,修改結果可以傳遞回調用處。這樣就不必象Pascal那樣使用兩種參數傳遞機制。</P><H2>9.2 Python作用域與名字空間</H2><P>在引入類之前,我們必須講一講Python的作用域規則。類定義很好地利用了名字空間,需要了解Python如何處理作用域和名字空間才能充分理解類的使用。另外,作用域規則也是一個高級Python程序員必須掌握的知識。</P><P> 先給出一些定義。<P>名字空間是從名字到對象的映射。多數名字空間目前是用Python字典類型實現的,不過這一點一般是注意不到的,而且將來可能會改變。下面是名字空間的一些實例:Python中內置的名字(如abs()等函數,以及內置的例外名);模塊中的全局名;函數調用中的局部變量名。在某種意義上一個對象的所有屬性也構成了一個名字空間。關于名字空間最重要的事要知道不同名字空間的名字沒有任何聯系;例如,兩個不同模塊可能都定義了一個叫“maximize”的函數而不會引起混亂,因為模塊的用戶必須在函數名之前加上模塊名作為修飾。</P><P>另外,在Python中可以把任何一個在句點之后的名字稱為屬性,例如,在表達式z.real中,real是一個對象z的屬性。嚴格地說,對模塊中的名字的引用是屬性引用:在表達式modname.funcname中,modname是一個模塊對象,funcname是它的一個屬性。在這種情況下在模塊屬性與模塊定義的全局名字之間存在一個直接的映射:它們使用相同的名字空間!</P><P>屬性可以是只讀的也可以是可寫的。在屬性可寫的時候,可以對屬性賦值。模塊屬性是可寫的:你可以寫“modname.the_answer = 42”。可寫屬性也可以用del語句閃出,如“del modname.the_answer”。</P><P>名字空間與不同時刻創建,有不同的生存周期。包含Python內置名字的名字空間當Python解釋程序開始時被創建,而且不會被刪除。模塊的全局名字空間當模塊定義被讀入時創建,一般情況下模塊名字空間也一直存在到解釋程序退出。由解釋程序的最頂層調用執行的語句,不論是從一個腳本文件讀入的還是交互輸入的,都屬于一個叫做__main__的模塊,所以也存在于自己的全局名字空間之中。(內置名字實際上也存在于一個模塊中,這個模塊叫做__builtin__)。</P><P>函數的局部名字空間當函數被調用時創建,當函數返回或者產生了一個不能在函數內部處理的例外時被刪除。(實際上,說是忘記了這個名字空間更符合實際發生的情況。)當然,遞歸調用在每次遞歸中有自己的局部名字空間。</P><P>一個作用域是Python程序中的一個文本區域,其中某個名字空間可以直接訪問。“直接訪問” 這里指的是使用不加修飾的名字就直接找到名字空間中的對象。</P><P>雖然作用域是靜態定義的,在使用時作用域是動態的。在任何運行時刻,總是恰好有三個作用域在使用中(即恰好有三個名字空間是直接可訪問的):最內層的作用域,最先被搜索,包含局部名字;中層的作用域,其次被搜索,包含當前模塊的全局名字;最外層的作用域最后被搜索,包含內置名字。</P><P>一般情況下,局部作用域引用當前函數的局部名字,其中局部是源程序文本意義上來看的。在函數外部,局部作用域與全局作用域使用相同的名字空間:模塊的名字空間。類定義在局部作用域中又增加了另一個名字空間。</P><P>一定要注意作用域是按照源程序中的文本位置確定的:模塊中定義的函數的全局作用域是模塊的名字空間,不管這個函數是從哪里調用或者以什么名字調用的。另一方面,對名字的搜索卻是在程序運行中動態進行的,不過,Python語言的定義也在演變,將來可能發展到靜態名字解析,在“編譯”時,所以不要依賴于動態名字解析!(實際上,局部名字已經是靜態確定的了)。</P><P>Python的一個特別之處是賦值總是進入最內層作用域。關于刪除也是這樣:“del x”從局部作用域對應的名字空間中刪除x的名字綁定(注意在Python中可以多個名字對應一個對象,所以刪除一個名字只是刪除了這個名字與其對象間的聯系而不一定刪除這個對象。實際上,所有引入新名字的操作都使用局部作用域:特別的,import語句和函數定義把模塊名或函數名綁定入局部作用域。(可以使用global語句指明某些變量是屬于全局名字空間的)。</P><H2>9.3 初識類</H2><P> 類引入了一些新語法,三種新對象類型,以及一些新的語義。<H3>9.3.1 類定義語法</H3><P> 類定義的最簡單形式如下:<PRE>class 類名: <語句-1> . . . <語句-N></PRE><P>如同函數定義(def語句)一樣,類定義必須先執行才能生效。(甚至可以把類定義放在if語句的一個分支中或函數中)。在實際使用時,類定義中的語句通常是函數定義,其它語句也是允許的,有時是有用的――我們后面會再提到這一點。類內的函數定義通常具有一種特別形式的自變量表,專用于方法的調用約定――這一點也會在后面詳細討論。</P><P>進入類定義后,產生了一個新的名字空間,被用作局部作用域――于是,所有對局部變量的賦值進入這個新名字空間。特別地,函數定義把函數名與新函數綁定在這個名字空間。</P><P>當函數定義正常結束(從結尾退出)時,就生成了一個類對象。這基本上是將類定義生成的名字空間包裹而成的一個對象;我們在下一節會學到類對象的更多知識。原始的局部作用域(在進入類定義之前起作用的那個)被恢復,類對象在這里被綁定到了類對象定義頭部所指定的名字。</P><H3>9.3.2 類對象</H3><P>類對象支持兩種操作:屬性引用和實例化。屬性引用的格式和Python中其它的屬性引用格式相同,即obj.name。有效的屬性名包括生成類對象時的類名字空間中所有的名字。所以,如果象下面這樣定義類:</P><PRE>class MyClass: "A simple example class" i = 12345 def f(x): return 'hello world'</PRE><P>則MyClass.i和MyClass.f都是有效的屬性引用,分別返回一個整數和一個函數對象。也可以對類屬性賦值,所以你可以對MyClass.i賦值而改變該屬性的值。</P><P>__doc__也是一個有效的屬性,它是只讀的,返回類的文檔字符串:“A simple example class”。</P><P>類實例化使用函數記號。只要把這個類對象看成是一個沒有自變量的函數,返回一個類實例。例如(假設使用上面的類):</P><PRE> x = MyClass()</PRE><P>可以生成該類的一個新實例并把實例對象賦給局部變量x。 <H3>9.3.3 實例對象</H3><P> 我們如何使用實例對象呢?類實例只懂得屬性引用這一種操作。有兩類有效的屬性。 <P>第一類屬性叫做數據屬性。數據屬性相當于Smalltalk中的“實例變量”,和C++中的“數據成員”。數據成員不需要聲明,也不需要在類定義中已經存在,象局部變量一樣,只要一賦值它就產生了。例如,如果x是上面的MyClass類的一個實例,則下面的例子將顯示值16而不會留下任何痕跡:</P><PRE>x.counter = 1while x.counter < 10: x.counter = x.counter * 2print x.counterdel x.counter</PRE><P>類實例能理解的第二類屬性引用是方法。方法是“屬于”一個對象的函數。(在Python中,方法并不是只用于類實例的:其它對象類型也可以有方法,例如,列表對象也有append、insert、remove、sort等方法。不過,在這里除非特別說明我們用方法來特指類實例對象的方法)。</P><P>類對象的有效方法名依賴于它的類。按照定義,類的所有類型為函數對象屬性定義了其實例的對應方法。所以在我們的例子y,x.f是一個有效的方法引用,因為MyClass是一個函數;x.i不是方法引用,因為MyClass.i不是。但是x.f和MyClass.f不是同一個東西――x.f是一個方法對象而不是一個函數對象。</P><H3>9.3.4 方法對象</H3><P> 方法一般是直接調用的,例如:<PRE> x.f()</PRE><P>在我們的例子中,這將返回字符串‘hello world’。然而,也可以不直接調用方法:x.f是一個方法對象,可以把它保存起來再調用。例如:</P><PRE>xf = x.fwhile 1: print xf()</PRE><P>會不停地顯示“hello world”。 <P>調用方法時到底發生了什么呢?你可能已經注意到x.f()調用沒有自變量,而函數f在調用時有一個自變量。那個自變量是怎么回事?Python如果調用一個需要自變量的函數時忽略自變量肯定會產生例外錯誤――即使那個自變量不需要用到……</P><P>實際上,你可以猜出答案:方法與函數的區別在于對象作為方法的第一個自變量自動傳遞給方法。在我們的例子中,調用x.f()等價于調用MyClass.f(x)。一般地,用n個自變量的去調用方法等價于把方法所屬對象插入到第一個自變量前面以后調用對應函數。</P><P>如果你還不理解方法是如何工作的,看一看方法的實現可能會有所幫助。在引用非數據屬性的實例屬性時,將搜索它的類。如果該屬性名是一個有效的函數對象,就生成一個方法對象,把實例對象(的指針)和函數對象包裝到一起:這就是方法對象。當方法對象用一個自變量表調用時,它再被打開包裝,由實例對象和原自變量表組合起來形成新自變量表,用這個新自變量表調用函數。</P><H2>9.4 一些說明</H2><P>在名字相同時數據屬性會覆蓋方法屬性;為了避免偶然的名字沖突,這在大型程序中會造成難以查找的錯誤,最好按某種命名慣例來區分方法名和數據名,例如,所有方法名用大寫字母開頭,所有數據屬性名前用一個唯一的字符串開頭(或者只是一個下劃線),或方法名用動詞而數據名用名詞。</P><P>數據屬性可以被方法引用也可以被普通用戶(“客戶”)引用。換句話說,類不能用來構造抽象數據類型。實際上,Python中沒有任何辦法可以強制進行數據隱藏——這些都是基于慣例。(另一方面,Python的實現是用C寫的,它可以完全隱藏實現細節,必要時可以控制對象存??;用C寫的Python擴展模塊也有同樣特性)。</P><P>客戶要自己小心使用數據屬性——客戶可能會因為隨意更改類對象的數據屬性而破壞由類方法維護的類數據的一致性。注意客戶只要注意避免名字沖突可以任意為實例對象增加新數據屬性而不需影響到方法的有效性——這里,有效的命名慣例可以省去許多麻煩。</P><P>從方法內要訪問本對象的數據屬性(或其它方法)沒有一個簡寫的辦法。我認為這事實上增加了程序的可讀性:在方法定義中不會混淆局部變量和實例變量。</P><P>習慣上,方法的第一自變量叫做self。這只不過是一個習慣用法:名字self在Python中沒有任何特殊意義。但是,因為用戶都使用此慣例,所以違背此慣例可能使其它Python程序員不容易讀你的程序,可以想象某些類瀏覽程序會依賴于此慣例)。</P>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -