Python类及面向对象编程【转】
Python類及面向對象編程
類是用來創建數據結構和新類型對象的主要機制.本章的主題就是類,面向對象編程和設計不是本章的重點。本章假定你具有數據結構的背景知識及一定的面向對象的編程經驗(其它面向對象的語言,比如java,c++).(參見第三章,類型和對象 了解對象這個術語及其內部實現的附加信息)
WeiZhong補充: 這本書出版于2001年,雖然Python有極佳的向下兼容性,但我們應該學習最新的知識。本章很多地方已經明顯過時,為了保證大家學到新的知識并維持這本書的完整性,我會在必要的地方說明哪些地方已經過時,哪些地方新增了功能。 Python從2.2起引入了new-style對象模型,以逐步替代已經使用多年的classic對象模型。 由于 classic class 已經行將廢止,所以我對本章的例子均作為了適當的修改以支持 new-style對象模型。參考文檔:《Python In a Nutshell》中的一節Python中的新型類及其實例
class語句
一個類定義了一系列與其實例對象密切關聯的屬性.典型的屬性包括變量(也被稱為類變量)和函數(又被稱為方法).
class語句用來定義一個類.類的主體中語句在類定義同時執行.(如 Listing 7.1)
Listing 7.1 類
Toggle line numbers 1 class Account(object): 2 "一個簡單的類" 3 account_type = "Basic" 4 def __init__(self,name,balance): 5 "初始化一個新 Account 實例" 6 self.name = name 7 self.balance = balance 8 def deposit(self,amt): 9 "存款" 10 self.balance = self.balance + amt 11 def withdraw(self,amt): 12 "取款" 13 self.balance = self.balance - amt 14 def inquiry(self): 15 "返回當前余額" 16 return self.balance訪問類屬性
類對象作為一個名字空間,存放在類定義語句運行時創建的對象.例如,Account里的內容可以這樣訪問:
Account.account_type Account.__init__ Account.deposit Account.withdraw Account.inquiry需要注意的是, class語句并不創建類的實例(例如上邊的例子,并沒有創建任何帳戶).它用來定義所有實例都應該有的屬性.
在類中定義的常規方法的第一個參數總是該類的實例,通常這個參數記為self。你也可能用其它任何合法的變量名,不過為了符合慣例,你最好還是用self. 類中定義的變量,即類變量,如account_type, 它被所有該類的實例共享. 雖然類定義了一個名字空間,但這個名字空間并不是為類主體中的代碼服務的.因此在類中引用一個類的屬性必須使用類的全名:
Toggle line numbers 1 class Foo(object): 2 def bar(self): 3 print "bar!" 4 def spam(self): 5 bar(self) # 錯誤,引發NameError 6 Foo.bar(self) # 合法的最后,你不能定義一個不操作實例的方法:
Toggle line numbers 1 class Foo(object): 2 def add(x,y): 3 return x+y 4 a = Foo.add(3,4) # TypeError. 需要一個類實例作為第一個參數=======================================================================================
以下為WeiZhong增補部分:?靜態方法和類方法(Python2.2以上)
- 靜態方法:?可以直接被類或類實例調用。它沒有常規方法那樣的特殊行為(綁定、非綁定、默認的第一個參數規則等等)。你完全可以將靜態方法當成一個用屬性引用方式調用的普通函數。任何時候定義靜態方法都不是必須的(靜態方法能實現的功能都可以通過定義一個普通函數來實現). 有些程序員認為,當有一堆函數僅僅為某一特定類編寫時,將這些函數包裝成靜態這種方式可以提供使用上的一致性。
根據python2.4最新提供的新語法,你可以用下面的方式創建一個靜態方法:
class AClass(object): @staticmethod #靜態方法修飾符,表示下面的方法是一個靜態方法 def astatic( ): print 'a static method' anInstance = AClass( ) AClass.astatic( ) # prints: a static method anInstance.astatic( ) # prints: a static method注:staticmethod是一個內建函數,用來將一個方法包裝成靜態方法,在2.4以前版本,只能用下面這種方式定義一個靜態方法(不再推薦使用):
class AClass(object): def astatic( ): print 'a static method' astatic=staticmethod(astatic)這種方法在函數定義本身比較長時經常會忘記后面這一行.
- 類方法?一個類方法就可以通過類或它的實例來調用的方法, 不管你是用類來調用這個方法還是類實例調用這個方法,該方法的第一個參數總是定義該方法的類對象。?記住:方法的第一個參數都是類對象而不是實例對象.?按照慣例,類方法的第一個形參被命名為 cls. 任何時候定義類方法都不是必須的(類方法能實現的功能都可以通過定義一個普通函數來實現,只要這個函數接受一個類對象做為參數就可以了).?你可以象下面這樣來生成一個類方法:
注:classmethod是一個內建函數,用來將一個方法封裝成類方法,在2.4以前版本,你只能用下面的方式定義一個類方法:
class AClass(object): def aclassmethod(cls): print 'a class method' aclassmethod=classmethod(aclassmethod)并沒有人要求必須封裝后的方法名字必須與封裝前一致,但建議你總是這樣做(如果你使用python2.4版本以下時).?這種方法在函數定義本身比較長時經常會忘記后面這一行.
=======================================================================================
增補部分至此結束
類實例
像調用函數一樣調用類,可以得到類的實例。生成實例的過程會自動調用類的__init__方法(如果你的類定義了這個方法的話)。
Toggle line numbers 1 # 創建一些帳戶 2 a = Account("Guido", 1000.00) # 調用 Account.__init__(a,"Guido",1000.00) 3 b = Account("Bill", 100000000000L)實例創建之后,就可以使用點(.)操作符來訪問它的屬性和方法:
Toggle line numbers 1 a.deposit(100.00) # 調用 Account.deposit(a,100.00) 2 b.withdraw(sys.maxint) # 調用 Account.withdraw(b,sys.maxint) 3 name = a.name # 得到帳戶名稱 4 print a.account_type # 顯示帳戶類型在系統內部,每個類實例都擁有一個字典(即實例的?__dict__?屬性,在第三章中有介紹).這個字典包含每個實例的信息.例如:
>>> print a.__dict__ {'balance': 1100.0, 'name': 'Guido'} >>> print b.__dict__ {'balance': 97852516353L, 'name': 'Bill'}若一個實例的屬性被修改,這個字典也隨之改變.上例中,屬性通過Account類中定義的方法__init()__, deposit(),以及withdraw()中對self變量賦值被改變.?不過對于類實例可以隨時添加私有屬性。
a.number = 123456 # 把 'number' 加入到 a.__dict__屬性的賦值總是發生在實例字典中,而屬性訪問則比屬性賦值復雜一些。當訪問一個屬性的時候,解釋器首先在實例的字典中搜索,若找不到則去創建這個實例的類的字典中搜索,若還找不到就到類的基類中搜索(在后邊 '繼承' 一節中會講到),如果還找不到最后會嘗試調用類的__getattr__方法來獲取屬性值(若類中定義了該方法的話).如果這個過程也失敗,則引發AttributeError異常
引用記數與實例銷毀
所有實例都是引用記數的.若一個實例引用記數變成零,該實例就被銷毀.當實例將被銷毀前,解釋器會搜索該對象的?__del__方法并調用它。但在實際應用中,極少有需要給一個類定義__del__方法, 除非這個對象在銷毀前需要執行一些清除操作(如關閉文件,斷開網絡,或者釋放其他系統資源).即使是在這種情況下,依賴__del__()來執行清除和關閉操作也是危險的,因為不能保證在解釋器關閉時會自動調用這個方法.更好的選擇是定義一個close()方法,在需要時顯式的調用這個方法來執行這個過程.?最后注意一點, 如果一個實例擁有__del__方法,則它永遠不會被Python的垃圾收集器回收(這也是不推薦定義?__del__()的理由).關于垃圾回收請參閱附錄A中的gc模塊。
有時會使用del語句來刪除對象的引用,如果這導致該對象引用記數變為零,就會自動調用__del__(). del語句并不直接調用__del__().
繼承
繼承(Inheritance)是創建新類的機制之一,它通過一個已有類進行修改和擴充來生成新類。這個原始的類被稱為基類(base class)或超類(superclass).新生成的類稱為該類的派生類(derived class)或子類(subclass).當通過繼承創建一個類時,它會自動'繼承'在基類中定義的屬性。一個子類也可以重新定義父類中已有的屬性或定義新的屬性.
Python支持多繼承,如果一個類有多個父類,在class語句中就使用逗號來分隔這個父類列表。例如:
Toggle line numbers 1 class D(oject): pass #D繼承自object 2 class B(D): #B是D的子類 3 varB = 42 4 def method1(self): 5 print "Class B : method1" 6 class C(D): #C也是D的子類 7 varC = 37 8 def method1(self): 9 print "Class C : method1" 10 def method2(self): 11 print "Class C : method2" 12 class A(B,C): #A是B和C的子類 13 varA = 3.3 14 def method3(self): 15 print "Class A : method3"當搜索在基類中定義的某個屬性時,Python采用深度優先的原則、按照子類定義中的基類順序進行搜索。**注意**(new-style類已經改變了這種行為)。上邊例子中,如果訪問?A.varB?,就會按照A-B-D-C-D這個順序進行搜索,只要找到就停止搜索.若有多個基類定義同一屬性的情況,則只使用第一個被找到屬性值:
Toggle line numbers 1 a = A() # 創建 'A' 的實例 2 a.method3() # 調用 A.method3(a) 3 a.method1() # 調用 B.method1(c) 4 a.varB # 得到 B.varB重要提示:新舊對象模型的差異:
注意:Python 中現在有兩種對象模型均在使用中即classic對象模型和new-style對象模型,也有兩種類:classic class 及 new-style class 在classic對象模型中,方法和屬性按 從左至右 深度優先 的順序查找(上文中已經提到).顯然,當多個父類繼承自同一個基類時,這會產生我們不想要的結果. 就上例來說,D是一個new-style類(繼承自object),B和C是D的子類, 而A是B和C的子類,如果按classic對象模型(原文中的提到的對象模型)的屬性查找規則是搜索順序是 A-B-D-C-D. 由于Python先查找D后查找C,即使C對D中的屬性進行了重定義,也只能使用D中定義的版本.這是classic數據模型的固有問題,在實際應用中會造成一些麻煩.為了解決這個及其它一些問題,Python從2.2版本開始引入new-style對象模型。 在new-style對象模型中,所有內建類型均是object的直接或間接子類. new-style對象模型改變了傳統對象模型中的解析順序,上面的例子我已經改寫為new-style類,因此,這個例子實際的搜索順序是 A-B-C-D. 每個內建類型及new-style類均內建有一個特殊的只讀屬性 __mro__,這是一個tuple,它保存著方法解析類型. 只能通過類來引用 __mro__(通過實例無法訪問). --WeiZhong Added@20060210如果一個子類定義了一個和基類具有相同名稱的屬性,則子類的實例將使用子類中定義的屬性.如果需要訪問原來的屬性,則必須使用全名來限制訪問區域:
Toggle line numbers 1 class D(A): 2 def method1(self): 3 print "Class D : method1" 4 A.method1(self) # 調用基類屬性需要注意的一點是子類實例的初始化.當一個子類實例被創建時, 基類的?__init__()方法并不會被自動調用.也就是子類必須自力更生來解決實例的初始化.例如:
Toggle line numbers 1 class D(A): 2 def __init__(self, args1): 3 # 初始化基類 4 A.__init__(self) 5 # 初始化自己 6 ...__del__()?與?__init__()?類似.
多態
Python通過上文中提到的屬性查詢規則來實現多態.當使用obj.method() 來訪問一個方法時,方法的搜索順序為:實例的?__dict__?屬性,實例的類定義,基類.?第一個被找到的方法被執行。
數據隱藏
默認情況下,所有的屬性都是'公開'的.這意味著一個類的所有屬性均可不受任何限制的訪問.這也意味著基類中定義的所有內容都能被子類繼承。?在面向對象編程實踐中,這種行為是我們不希望的。因為它不但暴露了對象的內部實現,而且容易在派生類對象及基類對象之間產生名字空間沖突。
要解決這個問題,只需要在類中將需要隱藏的屬性名字以兩個下劃線開頭,例如?__Foo。這樣系統會自動實時生成一個新的名字?_Classname__Foo?并用于內部使用。這樣在某種程度上就提供了私有屬性(其實這個?_Classname__Foo?仍然是不受限制訪問的嘿嘿),也解決了名字空間沖突的問題.例如:
Toggle line numbers 1 class A: 2 def __init__(self): 3 self.__X = 3 # self._A__X 4 5 class B(A): 6 def __init__(self): 7 A.__init__(self) 8 self.__X = 37 # self._B__X這是一個小技巧,并沒有真正阻止訪問一個類的*私有*屬性.如果已知一個類的名稱和它某個私有屬性的名稱,我們還是可以使用_Classname__Foo?來訪問到這個屬性.(這不是bug,因為在某些特定的場合這非常有用,比如調試時,所以系統一直保留這個所謂的*問題*)
操作符重載
用戶自定義對象可以通過在類中實現特殊方法(第三章中已介紹)來重載Python內建操作符.例如 Listing 7.2 中的類,它使用標準的數學運算符實現了復數的運算及類型轉換.
Listing 7.2 數學運算及類型轉換
Toggle line numbers 1 class Complex(object): 2 def __init__(self,real,imag=0): 3 self.real = float(real) 4 self.imag = float(imag) 5 def __repr__(self): 6 return "Complex(%s,%s)" % (self.real, self.imag) 7 def __str__(self): 8 return "(%g+%gj)" % (self.real, self.imag) 9 # self + other 10 def __add__(self,other): 11 return Complex(self.real + other.real, self.imag + other.imag) 12 # self - other 13 def __sub__(self,other): 14 return Complex(self.real - other.real, self.imag - other.imag) 15 # -self 16 def __neg__(self): 17 return Complex(-self.real, -self.imag) 18 # other + self 19 def __radd__(self,other): 20 return Complex.__add__(other,self) 21 # other - self 22 def __rsub__(self,other): 23 return Complex.__sub__(other,self) 24 # 將其他數值類型轉換為復數 25 def __coerce__(self,other): 26 if isinstance(other,Complex): 27 return self,other 28 try: # 檢測是否可以被轉換為浮點數 29 return self, Complex(float(other)) 30 except ValueError: 31 pass在這個例子中,有一些值得研究的地方:
-
首先__repr__()?用于返回對象的表達式字符串表示,這個返回字符串可以用于再次得到該對象.在本例中,會創建一個類似"Complex(r,i)"的字符串.另外__str__()方法創建一個字符串用于較美觀的輸出。(通常用于print語句)
然后,要處理復數在運算符左邊或右邊這兩種情況,必須同時提供?__op__()和?__rop__()方法.
最后,?__ceorco__?方法用于處理混合類型運算.在本例中,其他的數值類型均被轉換為復數,這樣才可以繼續進行復數的運算.
類,類型,和成員檢測
目前,類型和類是分開的.內建類型,如列表和字典是不能被繼承的,類也不能定義一個新類型.事實上,所有的類定義都屬于ClassType類型,同樣地,類的實例屬于InstanceType類型.所以,下面這個表達式對于兩個類永遠為真(即使這兩個實例是由不同的類創建的):?type(a) == type(b)
Python 2.4 已經支持內建類型的繼承,類與類型還有差別,但越來越微妙了。 對 new-style 類來說,類的實例并不是 InstanceType 類型。它的類型與類的名字有關。也因此,對new-style類來說,上面的等式只有同一個類的兩個不同實例才為真。 --WeiZhong內建函數isinstance(obj ,cname)用來測試obj對象是否是cname的實例。.如果是,函數就返回True.例如:
Toggle line numbers 1 class A(object): pass 2 class B(A): pass 3 class C(object): pass 4 5 a = A() # 'A'的實例 6 b = B() # 'B'的實例 7 c = C() # 'C'的實例 8 9 isinstance(a,A) # 返回 True 10 isinstance(b,A) # 返回 True, B 源自 A 11 isinstance(b,C) # 返回 False, C 與 A 沒有派生關系同樣地,內建函數issubclass(A ,B)用來測試類A是否是類B的子類:
issubclass(B,A) # 返回 True issubclass(C,A) # 返回 False issubclass(A,A) # 永遠返回Trueisinstance()函數也可以用于檢查任意內建類型:
Toggle line numbers 1 import types 2 isinstance(3, types.IntType) # 返回 True 3 isinstance(3, types.FloatType) # 返回 False這是一個被推薦的類型檢查方法,這樣類型和類的差別就可以忽略.
?
from:http://wiki.woodpecker.org.cn/moin/PythonEssentialRef7?
?
本文轉自張昺華-sky博客園博客,原文鏈接:http://www.cnblogs.com/sunshine-anycall/archive/2012/06/18/2554156.html,如需轉載請自行聯系原作者
總結
以上是生活随笔為你收集整理的Python类及面向对象编程【转】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 彻底卸载WinStdup
- 下一篇: 【网络与系统安全】关于SSL/TSL协议