編程思想: 面向過程:問題比較簡單,可以用線性的思維解決 面向對象:問題較為複雜,使用簡單的線性思維無法解決 兩種編程思想都是解決問題的方式,並不對立,通過面向對象的方式便於我們從巨集觀上把握事物之間的複雜關係、便於我們分析整個系統。本質仍然使用面向過程的方式來處理。 面向對象的程式設計強調把數據和操 ...
編程思想:
- 面向過程:問題比較簡單,可以用線性的思維解決
- 面向對象:問題較為複雜,使用簡單的線性思維無法解決
兩種編程思想都是解決問題的方式,並不對立,通過面向對象的方式便於我們從巨集觀上把握事物之間的複雜關係、便於我們分析整個系統。本質仍然使用面向過程的方式來處理。
面向對象的程式設計強調把數據和操作結合為一個不可分割的系統單位(即對象),對象的外部只需要知道它在做什麼,而不需要知道怎麼做。
類和對象:
類是抽象的模板,而實例是根據類創建出來的一個個具體的 ”對象“ ,每個對象都擁有相同的方法,但各自的數據可能不相同。
類:
類是多個類似事物組成的群體的總稱。能夠幫助我們快速理解和判斷事物的性質。
1 class 類名( object ): # 類名有一個或多個單詞構成,每個單詞首字母大寫其餘小寫,用下劃線連接 2 pass
Tip : object 表示該類是從哪個類繼承(後補充)下來的,通常,如果沒有合適的繼承類,就使用 object 類,這是所有類都會繼承的類。
數據類型:
- 不同的數據類型屬於不同的類。
- 可以使用 type() 函數來查看變數的數據類型。
對象:
對象是類的具體實例(instance),python 中一切皆對象。
類是一種數據結構,類定義數據類型的數據(屬性 )和行為(方法)。對象是類的具體實體,也可以稱為類的實例。需要註意,類的實例和實例對象有時不那麼容易區分但又不是同一個東西。在在用class 關鍵字創建自定義的類對象的時,創建的是類的實例;而後者是用類對象創建實例對象。
Python中一切皆對象;類定義完成後,會在當前作用域中定義一個以類名為名字,指向類對象的變數(名字)。
屬性:
Generally speaking, instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class:
類數據屬性:
類屬性是某個類所有實例共用的屬性,在創建實例對象時,每個實例都會擁有類數據屬性的一個拷貝。
實例變數屬性:
由類中__ init __() 方法定義的屬性(變數)。通過 “ . ”來訪問。在每個實例對象初始化時都會賦予獨有的數據。
舉幾個慄子:
1 class Student: 2 3 school = JXNU # class variable shared by all instances 4 5 def __init__(self, name): 6 self.name = name # instance variable unique to each instance
1 class Student: 2 school = 'JXNU' 3 4 def __init__(self, name): 5 self.name = name 6 7 8 stu1 = Student('Zhang') 9 stu2 = Student('Li') 10 11 print(stu1.school) # =>JXNU 12 print(stu2.school) # =>JXNU 13 14 print('-'*20) 15 Student.school = 'SDJU' 16 print(stu1.school) # =>SDNU 17 print(stu2.school) # =>SDNU 18 19 print('-'*20) 20 stu1.school = 'ZJNU' 21 print(stu2.school) # =>SDNU 22 print(stu1.school) # =>ZJNU 23 24 print('-'*20) 25 Student.school = 'PJNU' 26 print(stu1.school) # =>ZJNU 27 print(stu2.school) # =>PJNU
- 所有的實例初始化時都創建了一個指向同一個類屬性的指針。用類名Student修改類屬性school時,實例中的school也會發生改變。而用實例 stu1 修改對應的 school 時,指針將不指向原來的類屬性了,而是指向其他的變數。
- 類 Student 中,類屬性 school 為所有實例共用;實例屬性 name 為每個 Student 的實例獨有。
- 實例屬性是不能用類名訪問的屬性,必須由實例名 + ‘ . ' 來訪問。且每個實例對應一套。
私有屬性和公有屬性:
通常約定以兩個下劃線開頭而不以兩個下劃線結尾的變數為私有變數,其他的為公有變數。不能直接訪問私有變數,但可以在方法中訪問。
特殊屬性:
類屬性 |
含義 |
__name__ |
類的名字(字元串) |
__doc__ |
類的文檔字元串 |
__bases__ |
類的所有父類組成的元組 |
__dict__ |
類的屬性組成的字典 |
__module__ |
類所屬的模塊 |
__class__ |
類對象的類型 |
方法:
實例方法:
一般需要傳遞 self 參數用於調用實例的實例屬性(變數),且只能由實例訪問。沒有矛盾和衝突點不做贅述。
類方法:
- 用 @classmethod 修飾的方法,可以用類名直接訪問。第一個參數必須是當前類對象,該參數名一般約定為“cls”,通過它來傳遞類的屬性和方法(不能傳實例的屬性和方法)。
- 原則上,類方法是將類本身作為對象進行操作的方法。假設有個方法,這個方法在邏輯上採用類本身作為對象來調用更合理,那麼這個方法就可以定義為類方法。另外,如果需要繼承,也可以定義為類方法。
靜態方法:
- 用 @staticmethod 修飾的方法,可以用類名直接訪問。第一個參數必須是當前類對象,該參數名一般約定為“cls”,通過它來傳遞類的屬性和方法(不能傳實例的屬性和方法)。
- 靜態方法是類中的函數,不需要實例。靜態方法主要是用來存放邏輯性的代碼,邏輯上屬於類,但是和類本身沒有關係,也就是說在靜態方法中,不會涉及到類中的屬性和方法的操作。可以理解為,靜態方法是個獨立的、單純的函數,它僅僅托管於某個類的名稱空間中,便於使用和維護。
- 靜態方法更像是可以寫在類外的函數,但這樣是為了在邏輯上與類保持一定聯繫,不破壞類的邏輯性。
類方法和靜態方法的用途有時會混淆。
舉幾個慄子:
* 類方法慄子
1 情景: 2 假設我有一個學生類和一個班級類,想要實現的功能為: 3 執行班級人數增加的操作、獲得班級的總人數; 4 學生類繼承自班級類,每實例化一個學生,班級人數都能增加; 5 最後,我想定義一些學生,獲得班級中的總人數。 6 7 class ClassTest(object): 8 __num = 0 9 10 @classmethod 11 def addNum(cls): 12 cls.__num += 1 13 14 @classmethod 15 def getNum(cls): 16 return cls.__num 17 18 # 這裡我用到魔術方法__new__,主要是為了在創建實例的時候調用累加方法。 19 def __new__(self): 20 ClassTest.addNum() 21 return super(ClassTest, self).__new__(self) 22 23 24 class Student(ClassTest): 25 def __init__(self): 26 self.name = '' 27 28 a = Student() 29 b = Student() 30 print(ClassTest.getNum())
* 靜態方法慄子
1 import time 2 3 class TimeTest(object): 4 def __init__(self, hour, minute, second): 5 self.hour = hour 6 self.minute = minute 7 self.second = second 8 9 @staticmethod 10 def showTime(): 11 return time.strftime("%H:%M:%S", time.localtime()) 12 13 14 print(TimeTest.showTime()) 15 t = TimeTest(2, 10, 10) 16 nowTime = t.showTime() 17 print(nowTime)
實例屬性和方法的動態綁定:
話不多說,直接上代碼:
1 class Student: 2 def __init__(self, name): 3 self.name = name 4 5 6 stu1 = Student('Zhang') 7 stu2 = Student('Li') 8 9 stu1.age = 20 10 print(stu1.age) # =>20 11 print(stu2.age) # AttributeError: 'Student' object has no attribute 'age' 12 13 ### 追加的屬性或者方法只屬於這一實例不能用於共用。 ###
面向對象的三大特征:
封裝:
- 將數據(屬性)和方法打包到類對象中。在方法內部對屬性進行操作,在類對象的外部調用方法。這樣就無需關心方法內部的複雜實現,從而將實現和使用相分離。由類和對象來實現。
- 調用封裝內容可以直接用類名也可以創建一個類的實例。
繼承:
- 提高代碼的復用性。
- 將多個類共有的方法提取到父類中,子類僅需繼承父類而不必一一實現每個
1 class 父類(object): 2 def 父類中的方法(self): 3 # doing sth 4 5 6 class 子類(父類): # 子類繼承父類,即繼承了父類中的所有方法 7 pass 8 9 10 zi = 子類() # 創建子類的實例對象 11 12 zi.父類中的方法() # 執行從父類中繼承的方法
- 不同於Java、C++,Python允許多繼承。類繼承了多個類時尋找方法的方式有兩種,分別是深度優先和廣度優先。經典類多繼承是會按深度優先搜索方法;新式類則是按廣度優先搜索。這樣區分經典類和新式類呢?從字面上可以看出一個老一個新,新的必然包含了跟多的功能,也是之後推薦的寫法,從寫法上區分的話,如果當前類或者父類繼承了object類,那麼該類便是新式類,否則便是經典類。搜索有先後順序,所以不會產生多個同名方法的衝突。
多態:
- Pyhon不支持Java和C#這一類強類型語言中多態的寫法,但是原生多態,Python 本身就是一種多態語言。Python崇尚“鴨子類型”(duck typing)。
在程式設計中,鴨子類型(英語:duck typing)是動態類型的一種風格。在這種風格中,一個對象有效的語義,不是由繼承自特定的類或實現特定的介面,而是由當前方法和屬性的集合決定。這個概念的名字來源於由James Whitcomb Riley提出的鴨子測試,“鴨子測試”可以這樣表述:
“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。”
在鴨子類型中,關註的不是對象的類型本身,而是它是如何使用的。例如,在不使用鴨子類型的語言中,我們可以編寫一個函數,它接受一個類型為鴨的對象,並調用它的走和叫方法。在使用鴨子類型的語言中,這樣的一個函數可以接受一個任意類型的對象,並調用它的走和叫方法。如果這些需要被調用的方法不存在,那麼將引發一個運行時錯誤。任何擁有這樣的正確的走和叫方法的對象都可被函數接受的這種行為引出了以上表述,這種決定類型的方式因此得名。
鴨子類型通常得益於不測試方法和函數中參數的類型,而是依賴文檔、清晰的代碼和測試來確保正確使用。從靜態類型語言轉向動態類型語言的用戶通常試圖添加一些靜態的(在運行之前的)類型檢查,從而影響了鴨子類型的益處和可伸縮性,並約束了語言的動態特性。
1 class F1: 2 pass 3 4 5 class S1(F1): 6 7 def show(self): 8 print 'S1.show' 9 10 11 class S2(F1): 12 13 def show(self): 14 print 'S2.show' 15 16 def Func(obj): 17 print obj.show() 18 19 s1_obj = S1() 20 Func(s1_obj) 21 22 s2_obj = S2() 23 Func(s2_obj) 24 25 ### Python “鴨子類型” ###
小結:
- 面向對象是一種編程方式,此編程方式的實現是基於對 類 和 對象 的使用
- 類 是一個模板,模板中包裝了多個“函數”供使用
- 對象,根據模板創建的實例(即:對象),實例用於調用被包裝在類中的函數
- 面向對象三大特性:封裝、繼承和多態