類和對象
類是一種數據結構,可以包含數據成員和函數成員。程序中可以定義類,並創建和使用其對象實例。
面向對象概念
面向對象的程序設計具有三個基本特徵:封裝、繼承、多態,可以大大的增加程序的可靠性、代碼的可重用性和程序的可維護性,從而提高程序開發效率。
對象的定義
所謂對象,從概念層面講,就是某種事物的抽象(功能)。抽象原則包括數據抽象和過程抽象兩個方面:數據抽象就是定義對象屬性;過程抽象就是定義對象操作。
面向對象的程序設計強調把數據(屬性)和操作(服務)結合爲以一個不可分的系統單位(即對象),對象的外部只需要知道它做什麼,而不必知道它如何做
從規格層面講,對象是一系列可以被其他對象使用的公共接口(對象交互)。從語言實現層面來看,對象封裝了數據和代碼(數據和程序)
封裝
封裝是面向對象的主要特性。所謂封裝,也就是把客觀事物抽象並封裝成對象,即將數據成員,屬性,方法和事件等集合在一個整體內。通過訪問控制,還可以隱藏內部成員,只允許可信對象訪問或操作自己的部分數據或方法。
封裝保證了對象的獨立性,可以防止外部程序破壞對象的內部數據,同時便於程序的維護和修改
繼承
繼承是面向對象的程序設計中代碼重用的主要方法。繼承是允許使用現有類的功能,並在無須重新改寫原來的類的情況下,對這些功能進行擴展。繼承可以避免代碼複製和相關代碼維護問題
多態性
派生類具有基類的所有非私有數據和行爲以及新類自己定義的所有其他數據或行爲,即子類具有兩個有效類型:子類的類型及繼承的基類的類型。
對象可以表示多個類型的能力稱爲多態
多態性允許每個對象以自己的方式去響應共同消息,從而允許用戶以更明確的方式建立通用軟件,提高軟件開發的可維護性。
類對象和實例對象
類是一個數據結構,類定義數據類型的數據(屬性)和行爲(方法)。對象是類的具體實體,也可以稱爲類的實例。
在Python語言中,類稱爲類對象:類的實例稱爲實例對象
類對象
類使用關鍵字
class
聲明。類的聲明格式如下:
class 類名:
類體
其中,類名爲有效的標識符,命名規則一般爲多少個單詞組成的名稱,每個單詞除第一個單詞大寫字母,其餘全是小寫,類體由縮進組成
定義在類體內的元素都是類的成員。類的主要成員包括兩種類型,即描述狀態的數據成員(屬性)和描述操作的函數成員(方法)
實例對象
類是抽象的,要使用類定義的功能,就必須實例化類,即創建類的對象。創建對象後,可以使用
.
運算符來調用其成員
注意:創建類的對象、創建類的實例、實例化類等說法是等價的,都說明以類爲模版生成了一個對象的操作
anObject = 類名(參數列表)
anObject.對象函數 或 anObject.對象屬性
屬性
類的數據是在類中定義的成員變量(域),用來存儲描述類的值,稱爲屬性。屬性可以被該類中定義的方法訪問,也可以通過類對象或實例對象進行訪問。而在函數體或代碼塊中定義的局部變量,則只能在其定義的範圍內進行訪問。
屬性實際上是類中的變量。Python變量不需要聲明,可直接使用。建議在類定義的開始位置初始化類屬性,或者在構造函數
(__int__)
中初始化實例屬性
實例屬性
通過
self.變量名
定義的屬性,稱爲實例屬性
,也稱爲實例變量
。類的每個實例都包含該類的實力變量的一個單獨副本,實例變量屬於特定的實例。實例變量在類的內部通過self
訪問,在外部通過對象實例訪問。
實例屬性一般在
__int__
方法中通過如下形式初始化:self.實例變量名=初始值
#在實例函數中,通過self訪問
self.實例變量名=值 #寫入
self.實例變量名 #讀取
#創建對象實例後,通過對象實例訪問
obj1=類名() #創建對象實例
obj1.實例變量名=值 #寫入
obj1。實例變量名 #讀取
類屬性
Python也允許聲明屬於類對象本身的變量,即類屬性,也稱爲類變量、靜態屬性、類屬性屬於整個類,不是特定實例的一部分,而是所有實例之間共享的一個副本
#類屬性一般在類體中通過如下形式初始化
類變量名=初始值
類名.類變量名=值 #寫入
類名.類變量名 #讀取
類屬性如果通過
obj.屬性名
來訪問,則屬於該實例的實力屬性,雖然類屬性可以使用對象實例來訪問,但這樣容易造成困惑。所以不建議不要這樣使用,而是應該使用標準的訪問方式:類名.類變量名
私有屬性和公有屬性
Python類的成員沒有訪問控制限制,這與其他面向對象的與語言不同。
通常約定
兩個下劃線開頭的,但是不以兩個下劃線結束的
屬性是私有的(private)
,其他爲公共的(public)
。不能直接訪問私有屬性,但可以在方法中訪問。
@property裝飾器
面向對象編程的封裝性原則要求不直接訪問類中的數據成員。Python中可以通過定義私有屬性,然後定義相應的訪問改私有屬性的函數,並使用
@property
裝飾器裝飾這些函數,程序可以把函數’當作’屬性來訪問,從而提供更加友好的訪問方式
@property裝飾器默認提供一個只讀屬性,如果需要,可以使用對應的
getter
、setter
和deleter
裝飾器實現其他訪問器函數。
property
的調用格式爲:property(fget=None,fset=None,fdel=None,doc=None)
其中,fget
爲``get訪問器;fset
爲set
訪問器;fdel
爲del
訪問器
特殊屬性
Python對象中包含許多以雙下劃線開始和結束方法,稱爲特殊屬性。常用的特殊屬性如下圖:
自定義屬性
Python中可以賦予一個對象自定義的屬性,即類定義中不存在的屬性。對象通過特殊屬性
__dict__
存儲自定義屬性。
>>> class C1:pass
...
>>> o = C1()
>>> o.name = 'custom name'
>>> o.name
'custom name'
>>> o.__dict__
{'name': 'custom name'}
>>>
通過重載
__getattr__
和__setattr__
,可以攔截對成員的訪問,從而自定義屬性的行爲。__getattr__
只有在訪問不存在的成員時纔會被調用,__getattribute__
攔截所有(包括不存在的成員)的獲取操作。在__getattribute__
中不要使用return self.__dict__[name]
來返回結果,因爲在訪問self.__dict__
時同樣會被__getattribute__
攔截,從而造成無限遞歸形成死循環
。
__getattr__(self,name) #獲取屬性,比__getattribute__優先調用
__getattribute__(self,name) #獲取屬性
__setattr__(self,name,value) #設置屬性
__delattr__(self,name) #刪除屬性
方法
實例方法
方法是與類相關的函數,類方法的定義與普通的函數一致。
一般情況下,
類方法的第一個參數一般爲self
,這種方法稱爲實例方法
。實例方法對類的某個給定的實例進行操作,可以通過self
顯示地訪問該實例。實例方法的聲明格式如下:
def 方法名(self,[形參列表]):
函數體
#方法調用格式如下:
對象.方法名([實參列表])
雖然類方法的第一個參數爲
self
,但調用時,用戶不需要也不能給該參數傳值。事實上,Python自動把對象實例傳遞給該參數。
#假設聲明瞭一個類MyCalss和類方法my_func(self,p1,p2),則:
obj1 = MyClass #創建MyClass的對象實例obj1
obj1.my_func(p1,p2) #調用對象obj1的方法
調用對象
obj1
的方法obj1.my_func(p1,p2)
,Python自動轉換爲:obj.my_func(obj1,p1,p2)
,即自動把對象實例obj1
傳值給self
參數
注意:Python中的
self
等價於C++
中的self
指針和Java
、C#
中的this
關鍵字。雖然沒有限制第一個參數名必須爲self
,但建議讀者遵循慣例,這樣便於閱讀和理解,且集成開發環境IDE
也會提供相應的支持
靜態方法
Python也允許聲明屬於與類的對象實例無關的方法,稱爲
靜態方法
。靜態方法不對特定實例進行操作,在靜態方法中訪問對象實例會導致錯誤。靜態方法通過裝飾器@staticmethod
來定義,其聲明格式如下:
@staticmethod
def 靜態方法名([形參列表]):
函數體
靜態方法一般通過類名來訪問,也可以通過對象實例來調用。其調用格式如下:
類名.靜態方法([實參列表])
類方法
Python也允許聲明屬於類本身的方法,即類方法。類方法不對特定的實例進行操作,在類方法中訪問對象實例屬性會導致錯誤。類方法通過
@staticmethod
來定義,第一個形式參數必須爲對象本身,通常爲cls
,類方法的聲明格式如下:
@classmethod
def 類方法名(cls,[形參列表]):
函數體
類方法一般通過類名來訪問,也可以通過對象實例來調用。其調用格式如下:
類名.類方法名([實參列表])
值得注意的是,雖然類方法的第一個參數爲cls
,但調用時,用戶不需要也不能給該參數傳值。事實上,Python自動把類類類對象傳遞給參數,類對象與類的實例對象不同,在Python中,類本身也是對象。調用子類繼承父類方法時,傳入cls
是子類對象,而非類對象。
__init__方法(構造函數)和__new__方法
Python類體中,可以定義特殊的方法:
__new__
方法__init__
方法。
__new__
方法是一個類方法,創建對象時調用,返回當前對象的一個實例,一般無須重載該方法
__init__
方法即構造函數(構造方法),用於執行類的實例的初始化工作。創建完對象後調用,初始化當前對象的實例,無返回值
__del__方法(析構函數)
Python類體中,可以定義一個特殊的方法:__del__方法。
__del__
方法即析構函數(析構方法),用於實現銷燬類的實例所需的操作,如釋放對象佔用的非託管資源(例如:打開的文件、網絡連接等)
默認情況下。當對象不再被使用時,__del__
方法運行,由於Python解釋器實現自動垃圾回收,即無法保證這個方法究竟在什麼時候運行
通過del
語句,可以強制銷燬一個對象實例,從而保證調用對象實例的__del__
方法
私有方法與公有方法
與私有屬性類似,Python約定兩個下劃線開頭,但不以兩個下劃線結束的方法是私有方的
(private)
,與其他爲公有的(public)
。以下劃線開始和結束的是Python專有的特殊方法。不能直接訪問私有方法,但可以在其他方法中訪問。
方法重載
在其他程序設計語言中,方法可以重載,即可以定義多個重名方法,只要保證方法簽名是唯一的。方法簽名包括三個部分:
方法名
、參數數量
、參數類型
但Python本身是動態語言,方法的參數沒有聲明類型(調用傳值時確定參數的類型),參數的數量由可選參數和可變參數來控制。故Python對象方法不需要重載,定義一個方法既可以實現多種調用,從而實現相當於其他程序設計的預言重載功能。
在Python類體中,可以定義多個重名的方法,雖然不會報錯,但只有最後一個方法有效,所以建議不要定義重名的方法
繼承
Python支持多重繼承,即一個派生類可以繼承多個基類。派生類聲明格式如下:
class 派生類(基類,[基類2,....]):
類體
其中,派生類名後爲所有基類的名稱元組,如果在類定義中沒有指定基類,則默認其基類爲
object
。object
是所有對象的根基類,定義了公用方法的默認實現,如__new__()
class Foo: pass
= class Foo(object): pass
聲明派生類時,必須在其構造函數中調用基類的構造函數,調用格式如下:
基類名.__init__(self,參數列表)
查看繼承的層次關係
多個類的繼承可以形成層次關係,通過類的方法
mro()
或類的屬性__mro__
可以輸出其繼承的層次關係
>>> class A:pass
...
>>> class B(A):pass
...
>>> class C(B):pass
...
>>> class D(A):pass
...
>>> class E(B,D):pass
...
>>> D.mro()
[<class '__main__.D'>, <class '__main__.A'>, <class 'object'>]
>>> E.__mro__
(<class '__main__.E'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)
>>>
類成員的繼承和重寫
通過繼承,派生類繼承基類中除構造方法之外的所有成員。如果在派生類重新定義從基類繼承的方法,則派生類中定義的方法覆蓋從基類中繼承的方法
對象的特殊方法
對象的特殊方法概述
Python對象中包含許多以雙下劃線開始和結束的方法,稱爲特殊方法。特殊方法通常在針對對象的某種操作時自動調用
運算符重載與對象的特殊方法
Python的運算符實際上是通過調用對象的特殊方法實現的
>>> x=12;y=23
>>> x+y
35
>>> x.__add__(y)
35
>>>
在Python類體中,通過重寫各運算符對應的特殊方法,即可以實現運算符的重載
@functools.total_ordering裝飾器
支持大小比較的對象需要實現特殊方法:
__eq__
、__lt__
、__le__
、__ge__
、__gt__
。使用functools
模塊的total_ordering
裝飾器裝飾類,則只需要實現__eq__
,以及__lt__
、__le__
、__ge__
、__gt__
中的任意一個。total__ordering
裝飾器實現其他比較運算,以簡化代碼量
__call__方法和可調用對象
Python類體中可以定義一個特殊方法:
__call__
方法。定義了__cal___
方法的對象稱爲可調用對象(callabe),即該對象可以像函數一樣被調用。
對象的引用、淺拷貝和深拷貝
對象的引用
對象的賦值實際上是對象的引用,創建一個對象並把它賦值給一個變量,該變量是指向該對象的引用,其
id()
返回值保持一致。
對象的淺拷貝
對象的賦值引用同一個對象,即拷貝對象。如果要拷貝對象,可以使用下列方法之一:
- 切片操作
- 對象實例化
- copy模塊的copy函數
Python拷貝一般是淺拷貝,即拷貝對象時,對象中包含的子對象並不拷貝,而是引用同一個子對象。
對象的深拷貝
如果要遞歸拷貝對象中包含的子對象,可以使用
copy
模塊的deepcopy
函數