- 類是一種數據結構, 可用來定義對象, 對象再把數據值和行爲融合在一起, 編程形式上的現實世界的抽象實體
實例是類的一個具體信息(真正實物),創建一個實例的過程稱作實例化
當創建一個類時, 實際也就創建了一個自己的數據類型(2.2以後類型和類進行了統一)
新式類和舊式類最大的不同:有沒有從祖先類派生 (即新式類必須繼承至少一個父類(object
是默認父類), 舊式類不指定父類) - 類的簡單用法:當作名稱空間,作爲容器對象來共享名稱空間(名字空間容器)
一個類被定義的目的是爲了當作一個模塊來用 - 類的方法:只能被類實例所調用, 所有方法聲明中都有
self
參數(自動由解釋器傳遞), 代表實例對象本身(其他語言中是this
) __init__()
:相當於類構造器, 主要在實例化時被調用(相當於隱式調用), 實例化調用返回這個實例之前, 去執行一些特定代碼(設置初始值、初步診斷)
實例化時,__init__()
中self
參數接收實例對象(self=實例名
), 該調用不應返回任何對象- 靠繼承來進行子類化是創建和定製新類型的一種方式, 新的類將保有繼承類的所有特性而不會改動原來類的定義
- 如果子類沒有定義它的構造器, 基類的構造器將會被調用
- 當在子類構造器中顯式調用父類的構造器時, 也要顯式地把self傳入, 因爲我們是在一個子類中調用(而不是直接通過實例)
- 命名類(
class Xxx
)時通常由大寫字母打頭, 數據值應該使用名詞(email/times), 方法使用謂詞(update_date), 最好用駱駝記法(AddrBook) - 面向對象編程:實現了數據層與邏輯層的融合, 用一個可用以創建這些對象的簡單抽象層來描述, 類提供定義, 實例負責實現
- OOD(面向對象設計)不會特別要求面向對象編程語言, 如純結構化語言C, 不過當一語言內建OO特性, 開發會更方便高效
- OOP常用術語: 抽象/實現 封裝/接口 合成 派生/繼承/繼承結構 泛化/特化 多態 自省/反射
- 抽象: 指對現實世界問題和實體的本質表現、行爲和特徵建模, 建立一個相關的子集, 可以用於描述程序結構, 從而實現這種模型
- 封裝: 描述對數據/信息進行隱藏的概念, 它對數據屬性提供接口和訪問函數(python類屬性是公開的, 所以要提供相應接口防止偷數據)
- 合成: 擴充對類的描述, 使得多個不同類合成爲一個大的類來實現抽象模型, 形式:1)通過聯合關係 2)通過聚合(封裝的組件僅能用接口訪問)
- 派生: 描述子類的創建, 新類保留父類中的數據和行爲並可以獨立修改
- 繼承: 子類的屬性從祖先類繼承 多”代”派生就是一個繼承結構(想成”族譜”)
- 泛化: 所有子類與其父類及祖先類有一樣的特點
- 特化: 所有子類的自定義(即與祖先類不同的屬性)
- 多態: 對象如何通過他們共同的屬性和動作來操作及訪問, 表明了動態綁定的存在, 允許重載及運行時類型確定和驗證
- 自省: 即反射, 展示某對象是如何在運行期取得自身信息的(進行”手工檢查類型”的工作能力,
dir()
、type()
)
- 類與函數的聲明相似, 允許創建函數、閉包、方法等, 最不同在於調用函數會運行, 調用類是創建實例
類屬性:數據屬性(即所定義的類的變量), 方法(綁定/非綁定) - 屬性: 屬於一個對象的數據或者函數元素, 通過句點屬性標誌符
.
訪問
當訪問一個屬性時, 它同時也是一個對象, 擁有自己的屬性, 可以訪問
類屬性僅與其被定義的類相綁定
類(實例也適用)的自省:用dir()
或者類的__dict__
屬性 - 靜態變量(靜態數據): 這些數據與它們所屬的類對象綁定, 不依賴於任何類實例, 通常僅用於追蹤與類相關的值
- 特殊的類屬性(自帶):
字符串名__name__ 、字符串文檔__doc__、父類組成的元祖__bases__、屬性__dict__(訪問類屬性時的搜索源)、定義所在模塊__module__(模塊間的類繼承) 、對應的類(新式)__class__、Cls.__dict__['method'] 等價於 Cls.method
- 從類型對象取對象類型名的方便方法:
type(obj).__name__
- 當訪問一類屬性時, 解釋器將搜索
__dict__
, 若沒有則從基類集的字典中再搜(從左到右),對類的修改會影響此類的字典(但基類的不變) - 創建實例 -> 檢查
__init__()
方法 -> 返回實例化對象 -> 無引用計數時 -> 調用__del__()
方法 並運行垃圾對象回收機制 __new__()
:真正的”構造器”方法(是類方法, 需要顯式傳入類), 返回一個合法的實例, 傳入的參數是在類實例化操作時生成的,會調用父類的__new__()
(會向上代理)__del__()
:”解構器”方法, 只能被調用一次, 在實例釋放前提供特殊處理功能的方法, 實例對象的引用清除後才調用(不常實現, 實例很少顯式釋放)
不要忘記首先調用父類的__del__()
如果定義__del__()
的實例是某個循環的一部分, 垃圾回收器將不會終止這個循環(需要自己顯式調用del)- 實例屬性: 在構造(調用
__init__()
)時可以最早設置實例屬性, 還可帶默認參數(參數要求是不變的對象)
Python允許運行時創建實例屬性
實例屬性的__dict__
中只有實例屬性, 沒有類屬性或特殊屬性, 但dir()
可以有 - 如果定義了構造器, 它不應當返回任何對象, 因爲實例對象是自動在實例化調用後返回的
- 特殊的實例屬性:
來自哪個類__class__、自己的屬性__dict__
- 內建類型也是類, 也有可以通過
dir()
來獲取屬性, 但是沒有__dict__
- 類是類屬性的名字空間, 實例則是實例屬性的
- 類屬性訪問順序:實例中找 -> 類中找 -> 基類中找 (給一個與類屬性同名的實例屬性賦值(不可變類型), 可以有效的”隱藏”類屬性)
- 類屬性的修改會影響所有的實例(當一個實例在類屬性被修改前/後創建, 更新類的值生效), 所以慎用
在python中沒有明確方法來只是你想要修改同名的類屬性 self
變量是在類實例方法中引用方法所綁定的實例, 用於關聯類, 在方法調用中總是第一個參數- 綁定/非綁定:方法是類內部定義的函數, 方法只有在類有實例時才能被調用(綁定), 沒有實例時方法就是未綁定的
當沒有實例並且需要調用一個非綁定方法時必須傳遞self
函數
非綁定方法應用場景:派生一個子類時, 需要調用父類中的某方法 - 靜態方法: 不通過創建實例來調用方法的情況, 取代了用戶需要在全局名字空間中創建函數的做法; 不用在方法聲明中加參數
類方法: 需要第一個參數不是實例而是類, 由解釋器傳給方法(很多人用cls作爲變量名字)
用裝飾器@staticmathod/classmethod
等價於 靜態方法:staticmethod(method)
類方法:classmethod(method)
- 組合(合成):在一個類和其他類之間定義了一種“has-a”的關係 (例如C類有一個a類實例和一個b類實例)
- 可以包含其他對象的對象叫複合對象, 例如:列表,字典,類實例等
- 在一個層次的派生關係中相關類(垂直相鄰)是父類和子類關係, 同一個父類的派生類(水平相鄰)是同胞關係, 父類和所有高層類都被認爲是祖先
- 文檔字符串對類、函數/方法、模塊來說都是唯一的,
__doc__
不會從基類中繼承過來 - 如何調用被覆蓋的基類方法(P357):
給出子類實例調用未綁定的基類方法
在子類的重寫方法裏顯式調用基類方法(PaCls.method(self))
用super(Child, self).method()
- 從標準類型派生:不可變/可變類型(不可變類型需要標準類的
__new__()
方法) - 多重繼承關鍵點:正確找到沒有在子類(當前)類中定義的屬性; 正確調用對應父類方法及在子類中處理好自己的義務
MRO(Method Resolution Order):方法解釋順序; 每個類都有__mro__
屬性:由被搜索時的順序組成的元祖
由於在新式類中需要出現基類, 這樣就在繼承結構中形成了一個菱形, 使得屬性查找順序被改變了 - 經典類使用深度優先算法(先垂直找最親父類), 新式類的解釋順序是廣度優先(先水平找同胞兄弟)
- 與類/實例相關的BIF:
判斷是否派生類issubclass(sub, tuple(sup))
判斷是否爲實例(檢查類型)isinstance(ins, tuple(cls))
*attr(obj, 'attrname')
系列:
檢查屬性hasattr
, 取得屬性getattr
(三參) 賦予屬性setattr
(三參) 刪屬性delattr()
dir()
:
用於實例:顯示實例變量以及其類和基類的方法和類屬性
用於類: 顯示類以及它的所有基類中__dict__
的內容, 但不會顯示在元類中的類屬性
用於模塊:顯示模塊的__dict__
的內容
無參數: 顯示調用者的局部變量(即locals.keys()
)
除了實例變量名和常用方法外, 它還顯示那些通過特殊標記來調用的方法super(type, [obj])
:僅對新式類有效, 捕獲父類; 通過類名和顯式傳入self
(優點:不需要給出任何基類名字), 調用類方法時常用
如果obj是個實例isinstance(obj, type)
爲真
如果obj是個類或類型issubclass(obj, type)
爲真
如果希望父類被綁定, 可以傳入obj
參數
實際上super()
創造了一個super object
, 爲一個給定的類使用__mro__
去查找相應的父類vars()
:參數對象必須有__dict__
屬性, 返回一個包含了__dict__
中屬性和值的字典, 如果無參數則顯示locals()
- 定製類: 可用於 1)模擬標準類型 2)重載操作符 特殊方法一覽 P367
“原位”操作符:用一個i
代替星號的位置, 表示左結合操作與賦值的結合(num += num2)
__str__()
和__repr__()
實現定製類的字符串表示形式 (可以把__repr__()
作爲__str__()
的一個別名)
如果沒有__repr__()
, 不用print
進行內容顯示時輸出的是python的標準形式:<some_object_information>
self.__class__()
:調用實例化self
的那個類
__i*__()
:進行增量賦值後必須返回self
__iter__()
將一個對象聲明爲迭代器, 返回self
- 如果在重載情況下使用一個操作符卻沒有定義對應的特殊方法, 那麼會發生
TypeError
- 定製類迭代器的應用:無窮迭代(無損讀取序列) 任意項的迭代(根據參數控制條目)
- 雙下劃線(類元素級私有化):導入一個模塊時不能直接訪問到這些數據元素(防止在被外部導入時與同名變量相沖突),實際上會在名字前面加上下劃線和類名(
_ClsN__AttrN
)
但只是一種對導入源代碼無法獲得的模塊或對同一模塊中的其他代碼的保護機制 - 單下劃線(模塊級私有化): 可以防止被
from X import *
載入, 這是嚴格基於作用域的, 所以也適用於函數 - 包裝:對一個已存在的對象(無論是代碼塊或數據類型)進行包裝, 爲其增加/刪除/修改功能
包裝包括定義一個類, 它的實例擁有標準類型的核心行爲 - 授權:包裝的特性之一, 用於簡化處理相關命令性功能, 採用已存在的功能以達到最大限度的代碼重用
授權的過程即更新的功能都是由新類的某部分來處理, 但已存在的功能就授權給對象的默認屬性
關鍵方法:__getattr__()
,內建getattr()
, 當屬性不存在時獲取默認對象屬性 - 引用一個屬性時的搜索順序: 局部名稱空間 -> 類名稱空間 -> 對原對象進行授權請求,此時調用
__getattr__()
- 所有Python數值類型中只有複數擁有屬性
- 只有已存在的屬性是在此代碼中授權的, 特殊行爲沒有在類型的方法列表中, 不能被訪問, 因爲它們不是屬性(例如list的切片操作, 但可以通過訪問實際對象來解決)
- 布爾類型操作符的巧用法:
ans and 'nice' or 'good'
(如果ans爲真返回’nice’, 否則返回’good’) - 時間順序(chronological)數據:
創建時間ctime
:實例化的時間
修改時間mtime
:核心數據升級的時間(通常調用新的set()
方法)
訪問時間atime
:最後一次對象的數據值被獲取或者屬性被訪問時的時間戳
time.ctime
:接收分鐘數轉換成字符串格式(從1970年1月1日00:00:00開始算) - 新式類中最主要特性:能夠子類化Python數據類型, 使得原先內建的轉換函數現在都是工廠函數(當調用它們時, 實際上是在實例化)
結合該特性配合isinstance
使用更加清晰明朗, 但如果判斷對象是一個給定類型的實例或其子類的實例也會返回True
, 所以若要嚴格匹配, 需用is
__slots__
:類變量, 由一序列型對象組成, 由所有合法標誌構成的實例屬性的集合來表示(可以是列表/元祖/可迭代對象/字符串)
使用情況:當一個類的屬性數量很少, 但實例很多時,可以用其替代__dict__
利弊: 主要可以節約內存;但是會防止用戶隨意的動態增加實例屬性, 帶該屬性的類定義不會存在__dict__
了(除非在slots
中手動加入)__getattribute__()
:與__getattr__()
不同在於當屬性被訪問時, 它一直都可以被調用(而不是侷限於不能找到的情況)
如果在__getattribute__()
中再次調用了__getattribute__()
, 將會進入無窮遞歸
爲了避免無窮遞歸, 應該調用祖先類的同名方法:super(obj, self).__getattribute__(attr)
- 描述符:新式類中的關鍵要素之一, 爲對象屬性提供強大的API, 可認爲是表示對象屬性的一個代理
靜態方法、類方法、屬性、函數都是描述符
“描述符協議”:至少實現三個中的一個, 取值__get__()
, 賦值__set__()
, 刪值__delete__()
同時覆蓋__get__()
和__set__()
的類被稱作數據描述符
沒有實現__set__()
的類是方法描述符(非數據描述符)
整個描述符的心臟是__getattribute__()
, 高優先級, 用來查找類屬性的同時也是一個代理, 調用它可進行屬性的訪問等操作 - 若要爲一個屬性寫個代理, 必須把它作爲一個類的屬性, 讓這個代理來爲我們做所有的工作;當它處理對屬性的操作時, 會有一個描述符來代理全部的函數功能
- 描述符優先級別: 類屬性 > 數據描述符(有
get
和set
) > 實例屬性(局部對象dict
的值) > 非數據描述符(值不存在時提供一個值) > 默認__getattr__()
- 描述符會根據函數的類型確定如何封裝這個函數和函數被綁定的對象, 然後返回調用對象
函數本身就是一個描述符, 函數的__get__()
方法用來處理調用對象並返回結果 property(fget=None, fset=None, fdel=None, doc=None)
:
屬性是一種特殊類型的描述符, 用來處理所有對實例屬性的訪問
當用點屬性符號處理一個實例屬性時, 實際上是對實例的__dict__
屬性進行處理
優勢:它使用了方法, 可以寫一個和屬性有關的函數來處理實例屬性的get
,set
,del
操作, 使代碼更明朗(實例屬性一多,那些特殊方法就顯得臃腫)
一般用法:寫在類定義中,以函數作爲參數, 在所在的類被創建時被調用
在調用函數時self
作爲第一個參數被傳入, 所以需要加一個僞變量把self
丟棄
P398有名字空間與property()
的例子- 元類(metaclass):用於創建類, 可理解爲類中類, 元類的實例是其他的類
執行類定義時, 解釋器必須知道這個類正確的元類, 查找(__metaclass__
)順序:
類屬性-父類-對象或類型中繼承-全局變量-沒有:定義爲傳統類(元類爲types.ClassType
)
元類通常傳遞三個參數(到構造器):類名、從基類繼承數據的元祖和(類的)屬性字典 - 元類的最終使用者是程序員, 可以通過定義一個元類來使程序員按某種方式實現目標類, 便於簡化工作並使程序符合特定標準
- 類的相關模塊:
User*
(UserList
,UserDict
,UserString
):提供給用戶需要的封裝類, 還可以作爲基類提供子類化和定製
operator
:標準操作符的函數接口
types
:定義所有Python對象的類型在標準Python解釋器中的名字
Python隨學隨記(9)—— OOP相關
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.