有人整個 Python 學習生涯都沒有搞明白的技術之一:面向物件。
先放美圖調整下心情。
Python 面向物件的程式設計Python 準確地說也是一門面向物件程式設計的語言,簡稱 OOP,咱已經知道在 Python 中所有的資料型別都是物件,除了 Python 設定好的以外,Python 允許程式開發者自己定義資料型別,這種由程式設計師自定的資料型別就是 類。
面向物件初學有門檻,學習請謹慎。
1 類的定義與使用類的定義語法格式如下:
class MyClass(): 程式碼塊 ... 程式碼塊
類名的第一個字母建議大寫,例如語法格式中的 MyClass。
1.1 定義類、屬性與方法類的內部包含屬性與方法,接下來咱定義一個 “人” 類。
# 定義人類class Person(): # 類的屬性 name = "橡皮擦" # 類的方法 def talk(self): print("say hello")
在上述程式碼中,Person 是類名稱,在這個類中定義了一個屬性與一個方法。類的內部定義方法與函式非常相似,但是注意在類內部定義的函式可不能在稱為函數了(是不是開始繞了),要叫做方法,因為只有類的物件才可以呼叫該方法。 方法定義時注意有一個引數為 self,牢記為固定寫法,在所有類內部的方法引數中,都要寫上 self 這個關鍵字。
1.2 屬性與方法的呼叫在呼叫屬性與方法之前,必須先定義一個類的物件,具體方式如下,這個操作也叫做 例項化,類的例項化操作之後就出現了物件。
物件 = 類名()
例如剛才已經定義好了一個人類,使用下述程式碼可以獲取一個人類的物件。
# 定義物件xiang = Person()
物件定義完畢就可以使用屬性與方法了。
class Person(): # 類的屬性 name = "橡皮擦" # 類的方法 def talk(self): print("say hello")xiang = Person()# 輸出物件print(xiang)# 輸出物件的屬性print(xiang.name)# 輸出物件的方法xiang.talk()
程式碼執行之後,輸出如下內容。
<__main__.Person object at 0x000002465F364B70>橡皮擦say hello
程式碼中的變數 xiang 就是 Person 類的一個物件,透過 xiang 物件可以讀取 Person 類內的 name 屬性與 talk 方法。
如果類還有其它的屬性與方法,使用相同的方式即可實現。
1.3 類的建構函式難度上在調高一點,建立類的同時希望初始一些資料進去,也就是初始化類,該內容是在類的內部編寫一個方法,這個方法是一個特殊的方法,在程式設計的過程中定義類的物件將自動執行這個方法。
初始化方法名稱是固定的 __init__,該方法在 init 左右各有兩個下劃線。類的初始化方法稱為 建構函式(剛說了類裡面叫做方法,自己就叫函數了,是不是迷糊了,這個還真沒辦法,大家都這麼叫)。
接下來編寫一個程式碼,當定義一個類的物件時候,預設給 Person 類的屬性 name 賦值。
class Person(): # 類的屬性 name = "橡皮擦" # 建構函式 def __init__(self, in_name): self.name = in_name # 類的方法 def talk(self): print("say hello")xiang = Person('teacher')# 輸出物件print(xiang)# 輸出物件的屬性print(xiang.name)# 輸出物件的方法xiang.talk()
上述程式碼做了一些簡單的變動,首先加入了 __init__ 建構函式,注意建構函式的引數有兩個,一個是 self,這個在類內部定義函式的時候是必須的,並且需要放在引數的最左邊,Python 在定義一個類的物件的時候會自動傳入這個引數 self。self 代表的類本身的物件。
建構函式中還有一個引數 in_name,如果設計了建構函式,並且有除了 self 以外的其它引數,那在定義 Person 物件的時候,必須傳遞該引數,傳遞進來的該引數透過 self.name 可以修改物件的屬性。
說起來很繞,簡單裡面就是每次當我們用類定義一個物件的時候,例如下述程式碼:
obj1 = Person()obj2 = Person()
上面定義了兩個物件,都是依據類 Person 定義的,self 這個引數在類的內部就表示具體是哪個物件。
如果還不理解,沒有問題,記住下面的話。
類宣告之後,相當於你自己定義了一個數據型別,你可以使用該種資料型別的變數,只是由於面向物件的概念,把這個變數叫做物件了,物件可以呼叫類的屬性和方法,一個類對應多個物件,那如何判斷具體是哪個物件在呼叫類內部的屬性或者方法呢,需要用到的就是 self 這個引數。
1.4 屬性初始值在本部分之前,在類內部設定一個初始值,直接用 name = "橡皮擦" 來完成了,學習完建構函式之後,你應該瞭解到通常在 Python 初始化資料時,一般放在 __init__ 方法內。
class Person(): # 建構函式 def __init__(self, in_name, in_age): # 屬性的初始化 self.name = in_name self.age = in_age # 類的方法 def talk(self): # 類中的屬性,在初始化之後可以透過 self.name 呼叫 print(self.name) print("say hello") def show_age(self): # 透過 self.age 呼叫初始化的年齡 print(self.age)xiang = Person('teacher', 19)# 輸出物件print(xiang)# 輸出物件的屬性print(xiang.name)# 輸出物件的方法xiang.talk()
2 封裝接下來要學習的是面向物件的三個基本特徵之一,封裝。
封裝簡單理解就行,先不要鑽進去,理解概念,理解概念。
剛才我們使用的屬性與方法都可以透過物件在類的外部訪問,這些叫做公有屬性與公有方法,但有些時候類內部的屬性和方法不希望被外部物件進行修改,需要引入私有屬性與私有方法相關概念,這種概念的引入導致了封裝概念的出現。
封裝就是封住類內部的東西,不叫你隨便用(其實是有辦法可以呼叫到的)。
2.1 私有屬性在類內部定義私有屬性非常簡單,是寫作上的技巧,只需要在屬性前面加上兩個下劃線即可,即 __name。
例如在人類中定義一個秘密變數為私有屬性。
class Person(): # 建構函式 def __init__(self, in_name, in_age): # 屬性的初始化 self.name = in_name self.age = in_age self.__secret = "我有程式碼潔癖" # 私有屬性 # 類的方法 def talk(self): # 類中的方法,可以訪問到私有屬性 print(self.__secret) print("say hello") def show_age(self): print(self.age)xiang = Person('teacher', 19)# 嘗試輸出物件的私有屬性print(xiang.__secret) # 報錯# 嘗試透過類的方法輸出私有屬性xiang.talk()
類的內部初始化好私有屬性之後,透過物件.屬性名發現無法呼叫到私有屬性,但是在類的內部是可以使用私有屬性的,這種操作就叫做封裝屬性。
2.2 私有方法有私有屬性,必然有私有方法,這兩個形式一樣的,在方法前面加上兩個下劃線,就是私有方法了。
class Person(): # 建構函式 def __init__(self, in_name, in_age): # 屬性的初始化 self.name = in_name self.age = in_age self.__secret = "我有程式碼潔癖" # 私有屬性 # 類的方法 def talk(self): # 類中的方法,可以訪問到私有屬性 print(self.__secret) print("say hello") # 類的私有方法 def __show_age(self): print(self.age)xiang = Person('teacher', 19)# 嘗試輸出物件的私有屬性# print(xiang.__secret) # 報錯# 嘗試透過類的方法輸出私有屬性xiang.__show_age() # 報錯
注意報錯的內容,能記住就記住,熟練地找到程式碼錯誤的前提就是你碰到的程式碼錯誤足夠多。
3 繼承學習繼承概念以前,有幾個新詞需要學習一下,首先類是可以繼承的,其中被繼承的類稱為父類或者基類,繼承的類稱為子類或者衍生類。使用類繼承最大的好處就是,父類實現的公有屬性或者方法在子類中不用重新設計了。
該內容也是說起來迷糊,先看一下語法格式。
# 定義個父類class BaseClassName(): 父類的程式碼塊class ChildClassName(BaseClassName): 子類的程式碼塊
繼承類的時候,括號內放置父類的名稱。
3.1 繼承的簡單應用宣告一個動物類,然後讓狗類繼承動物類。動物類有一個公有屬性叫做 name,一個公有方法叫做 sleep。
# 定義 Animal 類class Animal(): def __init__(self): self.name = "動物名稱" def sleep(self): print("動物都會睡覺")# Dog 類繼承自 Animal 類class Dog(Animal): passdog = Dog()print(dog.name)dog.sleep()
上述程式碼中的 Dog 類沒有任何屬性與方法,只是繼承了 Animal 類,就擁有了 Animal 類的公有屬性與公有方法。
該繼承方式,子類無法直接讀取父類的私有屬性或者方法,也就是下述程式碼是錯誤的。
# 定義 Animal 類class Animal(): def __init__(self): self.name = "動物名稱" self.__secret = "秘密" def sleep(self): print("動物都會睡覺")# Dog 類繼承自 Animal 類class Dog(Animal): passdog = Dog()print(dog.__secret)dog.sleep()
3.2 子類與父類有相同名稱的屬性或方法
在程式編寫的時候,子類也可以有自己的初始化方法,即 __init__ 方法,在這種情況下會出現子類中的屬性名、方法名與父類相同的情況,此時請以子類中的屬性值或方法為主。
# 定義 Animal 類class Animal(): def __init__(self): self.name = "動物名稱" self.__secret = "秘密" def sleep(self): print("動物都會睡覺")# Dog 類繼承自 Animal 類class Dog(Animal): def __init__(self): self.name = "狗" def sleep(self): print("狗會睡覺")# 父類的物件animal = Animal()animal.sleep()# 子類的物件dog = Dog()dog.sleep()
該內容如果擴充套件開來就是面向物件的三大特徵的最後一個 -- 多型。
3.3 子類用父類的方法使用 super 函式可以在子類中呼叫父類的方法,具體程式碼如下:
# 定義 Animal 類class Animal(): def __init__(self, a_name): self.name = a_name self.__secret = "秘密" def sleep(self): print("動物都會睡覺") def show(self): print("現在傳遞進來的名稱為" + self.name)# Dog 類繼承自 Animal 類class Dog(Animal): def __init__(self, a_name): # 呼叫父類物件的普通方法 # super().sleep() super().__init__("動物名稱" + a_name)# 父類的物件animal = Animal("普通動物")animal.show()# 子類的物件dog = Dog("大狗狗")dog.show()
在 Dog 類的建構函式中透過 super().__init__("動物名稱" + a_name) 修改了傳遞給父類的引數,此方案相當於透過 super 函式生成一個父類的物件,然後在呼叫父類的 __init__ 方法,實現對父類的初始化操作。
4 多型多型簡單理解是說父類與子類有相同方法,透過父類、子類創建出的物件呼叫相同的方法名出現不同的結果。更多時候多型是程式會根據物件自動去呼叫指定的方法,該內容具體程式碼實現如下: 首先定義一個函式,這個函式有一個引數即可。
def gogo(obj): obj.say()
該函式的引數可以為任意資料型別的物件,然後在定義兩個類,這兩個類中需都存在 say 方法。
class Dog(): def say(self): print("汪汪汪")class Cat(): def say(self): print("喵喵喵")# 該函式會透過傳進的物件進行判斷是呼叫哪個方法。def gogo(obj): obj.say()# 透過 Dog 定義一個物件dog = Dog()# 透過 Cat 定義一個物件cat = Cat()# 在 gogo 函式中傳遞 dog 物件gogo(dog)# 在 gogo 函式中傳遞 cat 物件gogo(cat)
以上程式碼當傳入函式體內部的物件更換時,輸出的資料不同,這種編碼的形式或者叫編碼的設計思路就是多型的一種展示。
簡單理解就是 同一方法因物件不同導致實現內容不同。
5 多重繼承上文講解的都是單一繼承關係,在實際編碼中很多時候會用到多重繼承,就是一個類繼承多個父類,語法結構如下:
class 子類名稱(父類1,父類2,父類3...): 類的程式碼塊
該內容不再進行擴充套件開講解,在多重繼承的時候,記住一句話就行,寫在前面的父類比寫在後面的父類優先順序要高,也就說如果父類中都出現了同一個方法,那子類優先選擇前面的父類,即上面語法格式中的 父類1。
6 物件的資料型別判斷使用 type 函式可以判斷某物件的資料型別,例如下述程式碼:
class Dog(): def say(self): print("汪汪汪")class Cat(): def say(self): print("喵喵喵")# 透過 Dog 定義一個物件dog = Dog()# 透過 Cat 定義一個物件cat = Cat()print(type(dog))print(type(cat))
輸出內容為:
7 isinstance 函式isinstance 函式可以判斷物件是否屬於某一個類,語法格式如下:
isinstance(物件,類) # 如果物件是由類例項化而來,返回 True,否則返回 Flase
該函式可以判斷出一個物件是否例項化自父類。
# 父類class Animal(): pass# 子類class Dog(Animal): def say(self): print("汪汪汪")# 子類class Cat(Animal): def say(self): print("喵喵喵")# 透過 Dog 定義一個物件dog = Dog()# 透過 Cat 定義一個物件cat = Cat()print(isinstance(dog,Dog)) # Trueprint(isinstance(dog,Animal)) # Trueprint(isinstance(cat,Animal)) # True
8 特殊屬性、方法在之前的課程中,使用 dir 函式作用於某一物件,會得到如下內容。
該內容存在大量的 __XXXX__ 的內容,這些就是一個物件中特殊的屬性與方法。
接下來列舉幾個。
__doc__ 獲取文件字串
如果一個類中聲明瞭文件字串,就是在類的開始用 """ 三引號定義了一些內容,例如下述程式碼:
class Animal(): """" 我是文件字串,相當於一個類的說明部分,其實我有標準的格式 橡皮擦在第一遍滾雪球的時候,就是不願意寫 """ passanimal = Animal()print(animal.__doc__)
__name__ 屬性 這裡留下一個思考題,就是自行查閱 __name__ 屬性是幹啥的,如果理解了,以後看到下面的程式碼不會問為什麼。
if __name__ == '__main__': 執行某些程式碼
特殊方法部分在第一遍滾雪球的時候,不用費力去學習了,段位還沒到,學了和沒學一樣,如果覺得非學不可,恰好是一個求索知識的機會,這時的學習會事半功倍的。
想學習可以自行找資料,關鍵詞為 __str__()、__repr__()、__iter__()。
9 總結面向物件,對於程式設計初學者來說,這個東西學了跟沒學一樣,你要堅信不只是你無法在第一遍學習的時候就完全掌握,橡皮擦老師一樣的,跟橡皮擦老師一起學習的老前輩同學也是一樣的,橡皮擦帶過的學生也是一樣的,大家都一樣,99%的人都一樣,堅持就對了,先知道在 Python 中也有類,也有物件就夠了,時間是學習最大的利器,打卡,打卡,每天學那麼一點點,3 個月後見。
最後一碗毒雞湯
前女友和我分手有兩個原因,一是我當時沒什麼錢,二是她猜到了我將來也不會有什麼錢。 O(∩_∩)O 哈哈~