#4 Python面向物件(三)
前言
前兩節講解了Python面向物件的思想和Python類中各種變數的含義以及區別。肯定有小夥伴會問,類初始化時是否可以傳入引數?如果有多個類中含有共同的函式方法,是否可以重複利用?本節就帶著這些問題來繼續深入類。Here We Go!
一、類的傳參
1.1 帶引數的初始化
還是以Doctor類為例,假如lisi是一位男性醫生,現在要求在生成lisi這個物件時傳入其性別。怎麼辦呢?
按照之前所學的,類似於函式的傳參,肯定是:
lisi = Doctor('male')
的確,類帶引數的初始化就是這麼用的,那麼再按照函式的思想,類原始碼應該這麼寫:
class Doctor(name): def talk(self): print('My gender is {0}'.format(self.name)) lisi = Doctor('male') lisi.talk() # 按照函式的思想,建立一個帶引數的類 # 這是錯誤的!!!!
你已經看到,程式碼裡已經標出這是錯誤的編寫方法,不信的話來執行一下:
Traceback (most recent call last): File "1.py", line 1, in <module> class Doctor(name): NameError: name 'name' is not defined # 竟然丟擲name未定義異常
這是為什麼呢?莫名其妙的竟然丟擲未定義異常,關於這個問題將會放到下面(類的性質)來講
那帶引數的類要怎麼編寫呢?使用特殊方法 __init__(注意:init左右兩邊都是兩個下劃線) ,先來看個例子:
class Doctor(): def __init__(self, name): ¦self.name = name def talk(self): ¦print('My gender is {0}'.format(self.name)) lisi = Doctor('male') lisi.talk() # 執行結果: My gender is male
通過上面的程式碼可以看出,要想建立一個帶引數的類,類裡面要建立一個__init__的方法,其實這個特殊的函式叫做建構函式,建構函式會在例項化類後默默執行一次,也就是說,只要例項化了一個類,那麼就已經運行了建構函式。建構函式通常用來傳入引數使用。以上就是類的傳參。
1.2 類例項化後執行順序
當一個類例項化後,類中的程式碼被首先執行,其次是建構函式裡的程式碼,再然後是被呼叫的函式被執行,最後是解構函式被執行(解構函式將放在類的特殊方法講)
class Doctor(): def __init__(self, name): ¦print('我是第二個被執行的') ¦self.name = name print('先執行我') def talk(self): ¦print('只有呼叫我時才執行我') lisi = Doctor('male') lisi.talk() # 執行結果: 先執行我 我是第二個被執行的 只有呼叫我時才執行我
從上面的程式碼中可以清晰的看到每個程式碼塊的執行順序
二、面向物件的性質
面向物件共有三大性質:封裝性、繼承性、多型性。這三大性質是一定要記住的,不僅要記住,更要理解它們:japanese_ogre:
2.1 封裝性
先舉個栗子:chestnut:哇,一所學校有教學樓、圖書館、宿舍樓,你作為這個學校的學生,你當然可以隨意使用這三個地點,但是外來人員恐怕就不能使用了,學校阻止外來人員的辦法是建立圍牆,將學校圍起來。這就是封裝的含義,建立圍牆圍學校(類)就是封裝,只有學生(類的物件)可以使用學校資源,這兩點加起來就是封裝性。
封裝性概念:把客觀事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信的類或者物件操作,對不可信的進行資訊隱藏。對內部資料進行了保護,防止程式其他部分使用或修改內部資料
總的來說,封裝性就是安全:heavy_plus_sign:自私
2.2 繼承性
還記得前言中帶的問題嗎?當多個類擁有同樣的方法時,是否可以只寫一次重複利用,這就是繼承的優勢。
繼承繼承,顧名思義,就是繼承,父親和兒子嘛
繼承性:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴充套件。
繼承者稱為:子類、派生類;被繼承者稱為:基類、父類、超類;在Python中,被繼承者更習慣稱為超類
說了這麼多枯燥難以理解的概念,其實只需要一些例子就夠了:
class Animal: ''' 所有動物的超類 ''' def eat(self): ¦print('動物都會吃飯') def talk(self): ¦print('動物都會叫') class Dog(Animal): ''' 繼承Animal ''' def smell(self): ¦print('狗的嗅覺靈敏') class Cat(Animal): ''' 繼承Animal ''' def tree(self): ¦print('貓會爬樹')
從上面的程式碼可以看到,原來類(超類) 括號裡面時超類啊,不是指引數。呼叫的例子:
dog = Dog()# 例項化dog cat = Cat()# 例項化cat dog.eat()# dog呼叫了超類Animal的方法 dog.talk() dog.smell()# dog呼叫自己的smell方法 cat.eat()# cat也是一樣的 cat.talk() cat.tree()
# 執行結果: 動物都會吃飯 動物都會叫 狗的嗅覺靈敏 動物都會吃飯 動物都會叫 貓會爬樹
通過繼承,可以大大增加程式碼的利用率。
人類社會中,繼承往往是長江後浪推前浪,一浪更比一浪強!Python中的子類當然也可以將超類拍死在沙灘上,那就是改寫超類方法方便自己
2.2.1 繼承性——改寫不帶引數的超類
class Animal: ''' 所有動物的超類 ''' def eat(self): ¦print('動物都會吃飯') def talk(self): ¦print('動物都會叫') class Dog(Animal): ''' 繼承Animal ''' def smell(self): ¦print('狗的嗅覺靈敏') def eat(self): ¦''' ¦改寫超類eat方法 ¦''' ¦print('吃肉!') dog = Dog()# 例項化dog dog.talk() dog.smell()# dog呼叫自己的smell方法 dog.eat()# 呼叫被改寫的eat方法
# 執行結果: 動物都會叫 狗的嗅覺靈敏 吃肉!
2.2.2 繼承性——改寫帶引數的超類
超類帶引數,子類不帶引數
''' 所有動物的超類 ''' def __init__(self, name, age): ¦self.name = name ¦self.age = age def eat(self): ¦print('動物都會吃飯') def talk(self): ¦print('動物都會叫') class Dog(Animal): ''' 繼承Animal ''' def smell(self): ¦print('狗的嗅覺靈敏') def eat(self): ¦''' ¦改寫超類eat方法 ¦''' ¦print('吃肉!') dog = Dog()# 例項化dog dog.eat() # 執行結果: Traceback (most recent call last): File "6.py", line 32, in <module> dog = Dog()# 例項化dog TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
執行上述程式碼,會丟擲name,age未定義異常,也就是說,當超類帶有引數時,子類例項化時也要參入超類的引數:
dog = Dog('wangwang',5)# 例項化dog,傳入超類需要的引數 dog.eat() # 執行結果: 吃肉!
超類帶引數,子類帶引數
class Animal: ''' 所有動物的超類 ''' def __init__(self, name, age): ¦self.name = name ¦self.age = age def eat(self): ¦print('動物都會吃飯') def talk(self): ¦print('動物都會叫') class Dog(Animal): ''' 繼承Animal ''' def __init__(self, gender): ¦self.gender = gender def smell(self): ¦print('狗的嗅覺靈敏') def eat(self): ¦''' ¦改寫超類eat方法 ¦''' ¦print('吃肉!')
看到這裡,你肯定會有疑問️,傳入幾個引數才會正確呢?超類需要兩個,子類需要一個,那就是三個嘍~~~(天真)
dog = Dog('wangwang',5, 'male')# 例項化dog dog.eat() # 執行結果: Traceback (most recent call last): File "7.py", line 35, in <module> dog = Dog('wangwang',5, 'male')# 例項化dog TypeError: __init__() takes 2 positional arguments but 4 were given
根據異常資訊可知,__init__()需要兩個引數,但是給了4個。
?????哪來的4個?????明明只傳入了3個
不管幾個了,既然需要兩個引數,那就是隻用傳入超類的引數啦~~~(天真)
dog = Dog('wangwang',5)# 例項化dog dog.eat() # 執行結果: Traceback (most recent call last): File "7.py", line 35, in <module> dog = Dog('wangwang',5)# 例項化dog TypeError: __init__() takes 2 positional arguments but 3 were given
根據異常資訊可知,__init__()需要2個引數,但是給了3個。
?????怎麼又成3個了?????
難不成傳入1個會變成2個?豈不是和2個引數對應了(嘿嘿:speak_no_evil:)
dog = Dog('wangwang')# 例項化dog dog.eat() # 執行結果: 吃肉!
終於正確了,那麼傳入的這個引數到底是哪個變數呢?
dog = Dog('wangwang')# 例項化dog print(dog.name) print(dog.age) print(dog.gender) # 執行結果: Traceback (most recent call last): File "7.py", line 36, in <module> print(dog.name) AttributeError: 'Dog' object has no attribute 'name' # 沒有name變數 # 這時應該已經猜出傳入的引數給gender變量了
dog = Dog('wangwang')# 例項化dog print(dog.gender) dog.eat() # 執行結果: wangwang 吃肉!
可以看到,的確是傳給了gender變量了,那麼為什麼只需要一個引數呢?超類的兩個引數不翼而飛?其實如果你理解了上一點中的重構方法,就會發現上面的程式碼中根本就是重構了超類了__init__()方法,已經和超類中的__init__()無關了
那如果超類的引數和子類的引數都需要呢?Python3中,使用super()方法將超類的建構函式複製到子類中,用法如下:
class Animal: ''' 所有動物的超類 ''' def __init__(self, name, age): self.name = name self.age = age def eat(self): print('動物都會吃飯') def talk(self): print('動物都會叫') class Dog(Animal): ''' 繼承Animal ''' def __init__(self, name, age, gender): super().__init__(name, age)# 使用super方法 self.gender = gender def smell(self): print('狗的嗅覺靈敏') def eat(self): ''' 改寫超類eat方法 ''' print('吃肉!') dog = Dog('wangwang', 5, 'male')# 例項化dog print(dog.name) print(dog.age) print(dog.gender) dog.eat() # 執行結果: wangwang 5 male 吃肉!
從上面程式碼中可以看到super()其實是超類的一個物件 ,上述程式碼的思想是:給子類傳入3個引數,前2個引數再傳給超類,從而將超類需要的引數傳入
2.3 多型性
什麼是多型呢?先舉個例子哇:今天班級大掃除,老師要分配工作,現有1~10共10個學生,老師肯定會這樣說:“1,2,3,4,5去掃地,6,7,8,9,10拖地”,絕對不會這樣說:“1去掃地,2去掃地,3去掃地,4去掃地,5去掃地,6去拖地,7去拖地,8去拖地,9去拖地,10去拖地”。這其實就是多型性,掃地是一個介面,多個人共用,拖地時一個介面,多個人共用。在Python中,多型的例子有很多,最常見的恐怕就是len()這個內建函數了,你會發現不論是字串,還是列表,或者元組等都可以使用len()來統計長度,類似這種介面複用的方法就體現了多型性。
多型性概念:允許你將父物件設定成為和一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作
class Animal: def __init__(self, name): ¦self.name = name class Dog(Animal): def eat(self): ¦print('{0} eat cat'.format(self.name)) class Cat(Animal): def eat(self): ¦print('{0} eat mouse'.format(self.name)) class Mouse(Animal): def eat(self): ¦print('{0} eat {0}'.format(self.name)) def eat(obj):# 一個介面,多種用法 obj.eat() dog = Dog('狗') cat = Cat('貓') mouse = Mouse('老鼠') eat(dog) eat(cat) eat(mouse)
# 執行結果: 狗 eat cat 貓 eat mouse 老鼠 eat 老鼠
通過以上程式碼應該可以很清晰的瞭解多型性
總結
Python面向物件的知識馬上就要結束了,還剩下特殊方法、Python2和Python3中類的不同之處,下次見~