編程思想:
- 面向程序:問題比較簡單,可以用線性的思維解決
- 面向物件:問題較為復雜,使用簡單的線性思維無法解決
兩種編程思想都是解決問題的方式,并不對立,通過面向物件的方式便于我們從宏觀上把握事物之間的復雜關系、便于我們分析整個系統,本質仍然使用面向程序的方式來處理,
面向物件的程式設計強調把資料和操作結合為一個不可分割的系統單位(即物件),物件的外部只需要知道它在做什么,而不需要知道怎么做,
類和物件:
類是抽象的模板,而實體是根據類創建出來的一個個具體的 ”物件“ ,每個物件都擁有相同的方法,但各自的資料可能不相同,
類:
類是多個類似事物組成的群體的總稱,能夠幫助我們快速理解和判斷事物的性質,
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 “鴨子型別” ###
小結:
- 面向物件是一種編程方式,此編程方式的實作是基于對 類 和 物件 的使用
- 類 是一個模板,模板中包裝了多個“函式”供使用
- 物件,根據模板創建的實體(即:物件),實體用于呼叫被包裝在類中的函式
- 面向物件三大特性:封裝、繼承和多型
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/500609.html
標籤:其他
