Python学习笔记:面向对象高级编程(上)
前言
最近在學習深度學習,已經跑出了幾個模型,但Pyhton的基礎不夠扎實,因此,開始補習Python了,大家都推薦廖雪峰的課程,因此,開始了學習,但光學有沒有用,還要和大家討論一下,因此,寫下這些帖子,廖雪峰的課程連接在這里:廖雪峰
Python的相關介紹,以及它的歷史故事和運行機制,可以參見這篇:python介紹
Python的安裝可以參見這篇:Python安裝
Python的運行模式以及輸入輸出可以參見這篇:Python IO
Python的基礎概念介紹,可以參見這篇:Python 基礎
Python字符串和編碼的介紹,可以參見這篇:Python字符串與編碼
Python基本數據結構:list和tuple介紹,可以參見這篇:Python list和tuple
Python控制語句介紹:ifelse,可以參見這篇:Python 條件判斷
Python控制語句介紹:循環實現,可以參見這篇:Python循環語句
Python數據結構:dict和set介紹Python數據結構dict和set
Python函數相關:Python函數
Python高階特性:Python高級特性
Python高階函數:Python高階函數
Python匿名函數:Python匿名函數
Python裝飾器:Python裝飾器
Python偏函數:Python偏函數
Python模塊:Python模塊
Python面向對象編程(1):Python面向對象
Python面向對象編程(2):Python面向對象(2)
Python面向對象編程(3):Python面向對象(3)
Python面向對象編程(4):Pyhton面向對象(4)
目錄:
- 前言
- 面向對象高級編程
- 使用slots
- 使用@property
- 多重繼承
- MixIn
- 小結
面向對象高級編程
數據封裝、繼承和多態只是面向對象程序設計中最基礎的3個概念。在Python中,面向對象還有很多高級特性,允許我們寫出非常強大的功能。
我們會討論多重繼承、定制類、元類等概念。
使用slots
正常情況下,當我們定義了一個class,創建了一個class的實例后,我們可以給該實例綁定任何屬性和方法,這就是動態語言的靈活性。先定義class:
class Student(object):pass然后,嘗試給實例綁定一個屬性:
>>> s = Student() >>> s.name = 'Mike' # 動態給實例綁定一個屬性 >>> print(s.name) Mike還可以嘗試給實例綁定一個方法:
>>> def set_age(self, age): # 定義一個函數作為實例方法 ... self.age = age ... >>> from types import MethodType >>> s.set_age = MethodType(set_age, s) # 給實例綁定一個方法 >>> s.set_age(25) # 調用實例方法 >>> s.age # 測試結果 25但是,給一個實例綁定的方法,對另一個實例是不起作用的:
對于實例,方法私有
為了給所有實例都綁定方法,可以給class綁定方法:
>>> def set_score(self, score): ... self.score = score ... >>> Student.set_score = set_score給class綁定方法后,所有實例均可調用:
>>> s.set_score(100) >>> s.score 100 >>> s2.set_score(99) >>> s2.score 99通常情況下,上面的set_score方法可以直接定義在class中,但動態綁定允許我們在程序運行的過程中動態給class加上功能,這在靜態語言中很難實現。
使用__slots__但是,如果我們想要限制實例的屬性怎么辦?比如,只允許對Student實例添加name和age屬性。
為了達到限制的目的,Python允許在定義class的時候,定義一個特殊的_slots_變量,來限制該class實例能添加的屬性:
class Student(object):__slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱然后,我們試試:
>>> s = Student() # 創建新的實例 >>> s.name = 'Michael' # 綁定屬性'name' >>> s.age = 25 # 綁定屬性'age' >>> s.score = 99 # 綁定屬性'score' Traceback (most recent call last):File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'由于’score’沒有被放到slots中,所以不能綁定score屬性,試圖綁定score將得到AttributeError的錯誤。
如果加入了方法,比如
雖然沒有增加,age這個屬性,但因為有了set_age方法,還是會增加age這個屬性。
使用slots要注意,slots定義限制屬性僅對當前類實例起作用,對繼承的子類是不起作用的:
除非在子類中也定義_slots_,這樣,子類實例允許定義的屬性就是自身的_slots_加上父類的_slots_
使用@property
在綁定屬性時,如果我們直接把屬性暴露出去,雖然寫起來很簡單,但是,沒辦法檢查參數,導致可以把成績隨便改:
s = Student() s.score = 9999這顯然不合邏輯。為了限制score的范圍,可以通過一個set_score()方法來設置成績,再通過一個get_score()來獲取成績,這樣,在set_score()方法里,就可以檢查參數:
class Student(object):def get_score(self):return self._scoredef set_score(self, value):if not isinstance(value, int):raise ValueError('score must be an integer!')if value < 0 or value > 100:raise ValueError('score must between 0 ~ 100!')self._score = value現在,對任意的Student實例進行操作,就不能隨心所欲地設置score了:>>> s = Student() >>> s.set_score(60) # ok! >>> s.get_score() 60 >>> s.set_score(9999) Traceback (most recent call last):... ValueError: score must between 0 ~ 100!但是,上面的調用方法又略顯復雜,沒有直接用屬性這么直接簡單。
有沒有既能檢查參數,又可以用類似屬性這樣簡單的方式來訪問類的變量呢?對于追求完美的Python程序員來說,這是必須要做到的!
還記得裝飾器(decorator)可以給函數動態加上功能嗎?對于類的方法,裝飾器一樣起作用。Python內置的@property裝飾器就是負責把一個方法變成屬性調用的:
@property的實現比較復雜,我們先考察如何使用。把一個getter方法變成屬性,只需要加上@property就可以了,此時,@property本身又創建了另一個裝飾器@score.setter,負責把一個setter方法變成屬性賦值,于是,我們就擁有一個可控的屬性操作:
>>> s = Student() >>> s.score = 60 # OK,實際轉化為s.set_score(60) >>> s.score # OK,實際轉化為s.get_score() 60 >>> s.score = 9999 Traceback (most recent call last):... ValueError: score must between 0 ~ 100!注意到這個神奇的@property,我們在對實例屬性操作的時候,就知道該屬性很可能不是直接暴露的,而是通過getter和setter方法來實現的。
這個@property實現了一個非常重要的功能,根據操作動態實現了不同的函數,函數對人是一樣的,但程序對其進行了分類,實現了智能操作。
還可以定義只讀屬性,只定義getter方法,不定義setter方法就是一個只讀屬性:
上面的birth是可讀寫屬性,而age就是一個只讀屬性,因為age可以根據birth和當前時間計算出來。
小結
@property廣泛應用在類的定義中,可以讓調用者寫出簡短的代碼,同時保證對參數進行必要的檢查,這樣,程序運行時就減少了出錯的可能性。
多重繼承
繼承是面向對象編程的一個重要的方式,因為通過繼承,子類就可以擴展父類的功能。
在對一個實體進行分類的時候,會有各種判斷標準,如果把每個標準都用上,類的設計就太麻煩了,因此,我們可以實現一個多重繼承,主分類和特定屬性分類。
回憶一下Animal類層次的設計,假設我們要實現以下4種動物:
如果按照哺乳動物和鳥類歸類,我們可以設計出這樣的類的層次:
┌───────────────┐│ Animal │└───────────────┘│┌────────────┴────────────┐│ │▼ ▼┌─────────────┐ ┌─────────────┐│ Mammal │ │ Bird │└─────────────┘ └─────────────┘│ │┌─────┴──────┐ ┌─────┴──────┐│ │ │ │▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Dog │ │ Bat │ │ Parrot │ │ Ostrich │ └─────────┘ └─────────┘ └─────────┘ └─────────┘但是如果按照“能跑”和“能飛”來歸類,我們就應該設計出這樣的類的層次:
┌───────────────┐│ Animal │└───────────────┘│┌────────────┴────────────┐│ │▼ ▼┌─────────────┐ ┌─────────────┐│ Runnable │ │ Flyable │└─────────────┘ └─────────────┘│ │┌─────┴──────┐ ┌─────┴──────┐│ │ │ │▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Dog │ │ Ostrich │ │ Parrot │ │ Bat │ └─────────┘ └─────────┘ └─────────┘ └─────────┘如果要把上面的兩種分類都包含進來,我們就得設計更多的層次:
哺乳類:能跑的哺乳類,能飛的哺乳類; 鳥類:能跑的鳥類,能飛的鳥類。這么一來,類的層次就復雜了:
┌───────────────┐│ Animal │└───────────────┘│┌────────────┴────────────┐│ │▼ ▼┌─────────────┐ ┌─────────────┐│ Mammal │ │ Bird │└─────────────┘ └─────────────┘│ │┌─────┴──────┐ ┌─────┴──────┐│ │ │ │▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ MRun │ │ MFly │ │ BRun │ │ BFly │ └─────────┘ └─────────┘ └─────────┘ └─────────┘│ │ │ ││ │ │ │▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Dog │ │ Bat │ │ Ostrich │ │ Parrot │ └─────────┘ └─────────┘ └─────────┘ └─────────┘如果要再增加“寵物類”和“非寵物類”,這么搞下去,類的數量會呈指數增長,很明顯這樣設計是不行的。
正確的做法是采用多重繼承。首先,主要的類層次仍按照哺乳類和鳥類設計:
class Animal(object):pass# 大類: class Mammal(Animal):passclass Bird(Animal):pass# 各種動物: class Dog(Mammal):passclass Bat(Mammal):passclass Parrot(Bird):passclass Ostrich(Bird):pass現在,我們要給動物再加上Runnable和Flyable的功能,只需要先定義好Runnable和Flyable的類:
class Runnable(object):def run(self):print('Running...')class Flyable(object):def fly(self):print('Flying...')對于需要Runnable功能的動物,就多繼承一個Runnable,例如Dog:
class Dog(Mammal, Runnable):pass對于需要Flyable功能的動物,就多繼承一個Flyable,例如Bat:
class Bat(Mammal, Flyable):pass通過多重繼承,一個子類就可以同時獲得多個父類的所有功能。
MixIn
在設計類的繼承關系時,通常,主線都是單一繼承下來的,例如,Ostrich繼承自Bird。但是,如果需要“混入”額外的功能,通過多重繼承就可以實現,比如,讓Ostrich除了繼承自Bird外,再同時繼承Runnable。這種設計通常稱之為MixIn。
為了更好地看出繼承關系,我們把Runnable和Flyable改為RunnableMixIn和FlyableMixIn。類似的,你還可以定義出肉食動物CarnivorousMixIn和植食動物HerbivoresMixIn,讓某個動物同時擁有好幾個MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):passMixIn的目的就是給一個類增加多個功能,這樣,在設計類的時候,我們優先考慮通過多重繼承來組合多個MixIn的功能,而不是設計多層次的復雜的繼承關系。
Python自帶的很多庫也使用了MixIn。舉個例子,Python自帶了TCPServer和UDPServer這兩類網絡服務,而要同時服務多個用戶就必須使用多進程或多線程模型,這兩種模型由ForkingMixIn和ThreadingMixIn提供。通過組合,我們就可以創造出合適的服務來。
比如,編寫一個多進程模式的TCP服務,定義如下:
class MyTCPServer(TCPServer, ForkingMixIn):pass編寫一個多線程模式的UDP服務,定義如下:
class MyUDPServer(UDPServer, ThreadingMixIn):pass如果你打算搞一個更先進的協程模型,可以編寫一個CoroutineMixIn:
class MyTCPServer(TCPServer, CoroutineMixIn):pass這樣一來,我們不需要復雜而龐大的繼承鏈,只要選擇組合不同的類的功能,就可以快速構造出所需的子類。
小結
由于Python允許使用多重繼承,因此,MixIn就是一種常見的設計。
只允許單一繼承的語言(如Java)不能使用MixIn的設計。
總結
以上是生活随笔為你收集整理的Python学习笔记:面向对象高级编程(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 退出所有循环_Python学习之路9—循
- 下一篇: php正则学习,php中正则表达式的学习