Pythin語言的面向對象(上)
1. 面向對象簡介
Python 是一門 面向對象 的 編程語言。
所謂 面向對象的語言,簡單理解就是:語言中的 所有操作 都是通過 對象 來進行的。
這麼解釋呢 還是有點抽象,那我們來打幾個 比方:你想做件事,你要先去找個 對象;你想要一個 數值,我先找一個 對象 來保存需要的 數值,之後再對 對象 進行調取;你想要一個 字符串,我先找一個 對象 來保存需要的 字符串,之後再對 對象 進行調取;你想要一個 函數,我要先有一個 函數對象 來保存需要的 函數,之後再對 對象 進行調取……回到最初,我們開篇講的第一句話:Python一切皆對象。這就是對 面向對象的語言 的簡單理解。
在我們進入 面向對象 的世界之前,我們先來對 計算機的編程思想 的發展簡單的瞭解一下。
面向對象 的 編程思想 是由 面向過程 的 編程思想 在實際操作過程中一步一步的 更新 和 迭代 衍化過來的。那什麼是 面向過程 呢?
面向過程:
面向過程 指將我們的程序 分解 爲一個一個的 執行步驟,通過對 每個步驟 的 抽象 來完成程序;
舉個例子:孩子想喫瓜了,怎麼辦?
1. 媽媽穿衣服穿鞋出門
2. 媽媽騎上電動車
3. 媽媽到超市門口放好電動車
4. 媽媽買西瓜
5. 媽媽 結賬
6. 媽媽騎電動車回家
7. 到家孩子喫西瓜
案例看完了,其實整個的這些步驟如果要編寫一成一個 程序,我們一般會按照 面向過程 的編寫方法進行編寫;從 媽媽穿衣服穿鞋出門 --> 到家孩子喫西瓜 這整個 過程 就是一個 面向過程 的編寫思想;這整個事情的 目的 呢,我們需要 7步 來完成實現。我們把實現這個 目的 編寫成了 第1步實現什麼,第2步實現什麼,第3步實現什麼……把整個事情分成了一個步驟一個步驟,這就是一個 面向過程 的編寫方式;整個這個過程,我們關注的不是“人”,不是“東西”……而是“事”。
回到我們現實生活,這整個的步驟和這種生活方式是沒什麼問題的;但是一旦到了我們 程序 中,我們寫完了一段代碼不可能就這麼完了對吧;更多可能的還是我們還需要 複用 的。假如,這一段代碼我們需要 複用 1000次,該怎麼辦呢?而且我們大家都知道:單純的 複用 不是最麻煩的,真正麻煩的是程序後期的 更新、維護 和 迭代。現在是“媽媽買瓜”;今天媽媽不在家,怎麼執行“爸爸買瓜”呢?再或者,外面現在在下暴雨,媽媽不能穿常規衣服去買瓜了,現在媽媽外面需要加套一件“雨衣”,這又該怎麼操作呢?等等等等,這是不是每出現一次 需求微調,我們是不是就要 重新編寫 代碼;這不僅給我帶來很多的不方便,更多的是對 代碼的正常運行 進行 不斷的挑戰。
面向過程 這種編寫方式往往 只適用 於 一個功能,如果要實現 別的功能 需重新編寫新的程序,往往對代碼的 複用性 比較 低。
這種編程方式 符合 人類的思維,編寫起來 比較容易。
假設上面這段 代碼 我一定要它實現別的功能,比如說 我一定要這段代碼 重複1000次,或者說我就是要 “孩子喫肉” 該怎麼辦?出門左轉,小編我是不是剛寫完三篇我對 Python語言的函數 的理解;我們把上面這段 代碼 放進 函數 裏,如果我想 執行1000次 直接調用或者循環 函數 1000次就可以了;我只要改原函數的一行代碼,這整個循環的1000次的代碼都改了。
這樣的操作沒什麼問題,而且 函數 的出現也是爲了來解決類似的問題而存在的,而單純 函數 的這個功能僅僅也只是增加的 函數複用性 這一個功能,而上面這個 函數 也只是在做一件事“孩子喫瓜”。如果我想要 “孩子喫肉”、“孩子喫蔬菜”、“孩子喫水果” 該怎麼辦?它們是不是有很多 相同 或者 相似 的地方;最起碼,它整體的運行框架是一致的,只是一些小細節是不一樣。講到這,有些一直追更的讀者可能會說:最近的一篇文章 10.Python語言的函數(下) 裏剛講過的 高級函數 和 Python語言的裝飾器 來擴展函數的功能;通過 高級函數 來對 孩子 的一些行爲進行進行管理;這麼操作,雖然需求效果是達到了,但是 高級函數 和 Python語言的裝飾器 是否又增加的程序的複雜性;所以,還是前面的那句話:這種編程方式 符合 人類的思維,編寫起來 比較容易,但是 功能 比較單一,不利於代碼的 複用性。
現在 面向過程 編程編寫出來的代碼的功能過於單一,用 函數 和 裝飾器 進行修改增加的程序的複雜性。那我們還有別的方法嗎?
還是用我們現實生活小事情來作爲實例:如果讀者朋友你現在是 一家之主,我們什麼事都要 操心,指揮別人做這個做那個。你說:孩兒他媽,你出門別忘了穿鞋啊;孩子他媽,你買東西別忘了騎車;孩子他媽,你買了這個瓜別忘了結賬……你是操碎了心,對吧?你這個孩他媽去買東西不能明搶對吧,我還是要結賬的對吧。所以,你這樣子在家肯定是天天要幹仗的對吧。
現在我們就有一種新的思維方式,你想輕鬆怎麼辦?是不是你只需要告訴別人做什麼就可以了。至於怎麼個過程,別的就不管了;比如我現在就跟孩子他媽說 一句話:給孩子買個西瓜。是不是就O了,對吧。那至於你穿沒穿鞋,開沒開車,結沒結賬,我就不管了。最終孩子他媽把西瓜買回來了;然後,叭,往孩子面前一擱:孩兒,喫吧,你媽給你買的。這不是OK了。這種行爲方式,在編程裏我們稱之爲:面向對象 的編程語言。
剛剛我們通過 面向過程 完成整個操作用了 7個步驟,那 面向對象 是不是隻是把這 7個步驟 變成 一個操作:孩子他媽給孩子買西瓜。完成。是不是就O了,很快速、簡單、簡潔。現在就是用 一行代碼 替換了 七行代碼 。作爲一個 立志 成爲 優秀計算機工程師 的你,會選擇用那種方法?毫無疑問,一定是 面向對象 的編程方法了。
那我們再來思考思考:這個 面向對象 我們面對的是誰?或者說這個 對象 是誰?函數?我?孩子?孩子他媽?西瓜?喫西瓜?再次回到我們的 起點:Python一切皆對象。其實對於 面向對象 的編程語言來說 一切都是對象。
舉幾個例子:“穿鞋” 這個動作是不是就是一個 對象;“電動車” 這個動作是不是就是一個 對象;“騎” 這個動作是不是就是一個 對象;包括“結賬” 這個動作是不是也是一個 對象……那麼大家會說:這麼多的 對象 有什麼好處?我們又怎麼把一系列的數據或者行爲保存到一個個的 對象 當中。
比如說:就給 孩子喫瓜 這個事情;我就可以把這個事情保存到 媽媽 這個 對象 當中,至於她怎麼去買,都是由這個 對象內部定義的信息決定 的。對象是什麼?對象 就是一個 存儲數據的容器;它的好處是什麼呀?好處就是:我想讓孩子喫瓜,我就讓孩子他媽去買瓜;我想讓孩子喫菜,我就讓孩子他媽去買菜……真正的細節就在 對象 裏,我不需要再操心了。什麼 穿鞋、開電動車、結賬……只要我們把 對象 寫好了、完善了,是不是就不需要再擔心了。
再比如說大家應該用過這個爬蟲的一個模塊:requests;requests.get(‘http://xxxxxxxxxxxx’)。這個 get(’ ') 裏面需要傳遞一個URL地址;此時你願意傳 百度 你就傳 百度,你願意傳 新浪 就傳一個 新浪,你願意傳一個 小說網站 你就傳一個 小說網站……具體細節我不管,我只是把這個對象給你定義好了,你直接用是不是就OK了。其實 這些操作 都是什麼?其實,這些操作就是 面向對象 操作。
那個此時大家心裏可能有一個 疑問:這個叫 孩子喫瓜 這件事,裏面 媽媽 這個對象中不也得是這麼一步一步實現這個步驟嗎?這個呢確實沒錯,但是 區別 在於什麼呀?區別在於 是不是這些 步驟 已經存在 於媽媽這個 對象當中 了。我們只要寫好這個 對象 是不是OK了,對吧?所以呢,我們來小小的總結一下:
1) 面向對象 的編程語言,關注 的是 對象,而 不注重 過程,對於 面向對象來說:一切皆對象;
2) 面向對象 的 編程思想,將 所有功能 統一保存 到 對應 的 對象 中,要使用某個功能,直接找到 對應的對象 即可;
3) 這種 編碼方式 比較 容易閱讀,並且 易於維護,容易複用。但是編寫的過程中 不太符合 常規的思維方式,剛開始編寫的時候相對會比較麻煩。
2. 類(class)
我們目前學習的 對象 都是 Python的內置對象,但是 內置對象 並不都能 滿足我們的需求,所以我們在開發的過程中經常要 自定義一些對象。
類 簡單理解:它就是相當於一個 圖紙。這個圖紙是幹嘛呀?我們以前說 對象 相當於是 一個容器,那麼你現在要 創建一個對象,或者你要自己 定義這麼一個對象。前面我們說:對象 就像是 容器,那這個這個 容器 是 方 的還是 圓 的?這個容器裏面是有 一個格、兩個格 還是 N個格……那這 屬性 些都是 由誰決定 的呢?由誰?是不是就是由我們說的這個 圖紙 來決定的,對吧。那麼你要創建這個 容器,那就得先選擇 圖紙;這個 圖紙 就是我們所說的這個 類 ;所以 類 簡單的理解 就是一張 圖紙,在程序中我們需要根據 類 來 創建對象;類 就是對象的 圖紙。
如果上面的這些介紹還沒有讓你完全明白,那我們來 舉個例子:現在呢,我們手上有張 圖紙,現在假設,這是一張可以造汽車的圖紙;這個時候,我們是不是可以根據這張 圖紙,我們造一輛 紅色汽車、一輛 藍色汽車、一輛 黑色汽車……對吧,那麼這些車是不是就是 對象,紅色汽車 一個 對象、藍色汽車 一個 對象、黑色汽車 一個 對象、等等等等;那這些對象是從哪來的?是不是根據 圖紙 造出來的。所以說我們可以這麼理解:我們又稱 對象 是 類 的 實例(instance)。“ 對象 是 類 的 實例 ”這句話我們又怎麼理解?我們說這張 圖紙 它只是一張 紙,它沒有什麼 實際的形態,但是我們可以 根據原圖紙來造出一個車,是不是就相當於把這張 圖紙 給他 實例 出來,就是有一個現實中大家可以看的到的東西。
所以,我們也稱:對象 是 類 的 實例(instance)。如果 多個對象 是通過 一個類 創建的,我們稱 這些對象 是:一類對象。這 一類對象 我們可以用前面的“造汽車”來理解:雖然 車 的 顏色 不一樣,但 車 的 型號 全部都是一樣的。再比如說你這個對象是通過“人”這個對象創建的,那就是一個 “人類”;假如這個對象是通過“狗”這個對象創建的,那就是一個 “狗類”……到此呢,類的介紹就已經差不多了;那我們現在再一起回顧一下:我們到現在都學過那些類?int() 、string() 、float() 、bool() 、list() ……那麼這些這些都是什麼?這些都是 類。爲什麼這麼說?來看 參考實例:
a = 1
print(a) # 1
a = int(1)
print(a) # 1
實例中,第一個a
是什麼?是不是一個整數;兩個print()
的結果都是一樣的吧,都是“整數 1”;只是這第二種方法我們平時寫的比較少,第二個a
的意思是不是:接收數據強制類型轉換爲整形。
那我們再來看一下:int() 、string() 、float() 、bool() 、list() ……這些Python語言內置的 類 有什麼特點?是不是他們都是 小寫字母開頭,(括號)結尾 的。所以,當我們需要 自定義類 的時候需要以 大寫字母 來開頭。當然,這是下一塊內容,我們等下再說。
回到前面,既然我們剛剛說:int()
是是一個類;那麼a = int(1)
這行代碼就今天就可以完整的理解:它是創建了一個int()
的類的實例。那這個int()
的類的實例我們怎麼理解?來看下面的 參考實例:
def fn_a():
pass
a = fn_a
print(a,type(a)) # <function fn_a at 0x0000028125B6D160> <class 'function'>
a = int(1)
print(a,type(a)) # 1 <class 'int'>
a = str('Python')
print(a,type(a)) # Python <class 'str'>
通過觀察 參考實例 的運行結果我們可以逐漸清晰:通過 type() 函數 返回對象的類型我們發現 a = int(1)
的對象類型是 class(類),class(類) 的屬性是 'int'
;而 a = fn_a
的對象類型是 function(函數)。雖然它們都很像,但是它們 不是一個東西。同樣的 a = str('Python')
的對象類型也是 class(類),class(類) 的屬性是 'str'
。
說這麼多,那讀者朋友們一定會問了:我們怎麼自己來定義一個 類 啊?其實,我們自定義一個 類 就非常簡單了;我們自己定義一個類就使用Python語言 內置的關鍵字 class。
語法實例:
# 創建 類 的語法:
class Class_name([父類]):
pass
我們本篇文章暫時不講類 () 裏的內容(下一篇),我們先講一個老生常談的話題:規範。在這裏,我們說當然也就是 類的命名規範。自定義的 類, 類 的名字要使用大寫字母開頭;常規的 類 的命名方法我們一般使用 大駝峯命名法
自定義 類 的 語法 以及 相關的注意事項 都已經講過了,那我們現在就一起來創建我們的第一個類吧。
class MyClass():
pass
print(MyClass) # <class '__main__.MyClass'>
我們已經成功的自己創建了第一個 類,並且通過print(MyClass)
打印出的結果得到:<class '__main__.MyClass'>
得到的結果是什麼意思呢?當前打印的結果是一個 類,__main__
表示當前的 類文件 是 主文件,運行在 主文件 裏這個 類 的名字叫MyClass
。
前面說過:類 就是 對象 的 圖紙。我們現在已經成功的完成一個 類 的創建了,那我們怎麼來創建一個 對象 呢?其實呢,這個操作也是非常的簡單。來看 參考實例:
class MyClass():
pass
mc = MyClass()
這個操作是什麼意思呢:我使用MyClass
這個 類 創建了一個對象,這個對象叫做 mc
;mc
就是通過 MyClass
這個 類 創建的 對象;mc
就是通過 MyClass
這個 類 的 實例(instance)。我們來通過 函數print()
和 函數type()
一起來看看 mc
打印出來的結果。
參考實例:
class MyClass():
pass
mc = MyClass()
print(mc,type(mc)) # <__main__.MyClass object at 0x000002698C928400> <class '__main__.MyClass'>
跳過解釋操作,直接翻譯結果的意思:首先解釋 print(mc)
出來的結果 <__main__.MyClass object at 0x000002698C928400>
;我們當前所在的 運行文件 是 主文件,打印的 變量mc
是 類 MyClass
實例化 後的 對象,存儲於 0x000002698C928400
這個內存位置當中。type(mc)
打印的是 變量mc
的類型,意思是 變量mc
是通過MyClass
這個 類 創建的 對象。
現在我們已經知道 如何創建一個 對象 已經 對象 打印出來的結果,那麼如果一個 類 被多個 對象 實例化 會怎麼樣呢?
參考實例:
class MyClass():
pass
mc_1 = MyClass()
mc_2 = MyClass()
mc_3 = MyClass()
mc_4 = MyClass()
看上面的 參考實例 我們會發現 4個對象 mc_1
、mc_2
、mc_3
、mc_4
其實都屬於一個 類 MyClass()
的實例,它們都是 一類對象;當然了,參考實例 畢竟相對的還是比較簡單,我們一眼就能看出來。不過,如果以後我們在真實的開發場景中如何證明 mc_1
、mc_2
、mc_3
、mc_4
4個對象都屬於 一類對象 呢?此時,我們來介紹 Python語言 中第一個對 類 進行操作的 函數 isinstance()
。
參考實例:
class MyClas():
pass
class MyClass():
pass
mc_1 = MyClass()
mc_2 = MyClass()
mc_3 = MyClass()
mc_4 = MyClass()
mc_5 = MyClas()
q = isinstance(mc_1,MyClass)
w = isinstance(mc_2,MyClass)
e = isinstance(mc_3,MyClass)
r = isinstance(mc_4,MyClass)
t = isinstance(mc_5,MyClass)
y = isinstance(int,MyClass)
print(q,w,e,r,t,y) # True True True True False False
通過觀察上面的 參考實例 我們會發現:如果 函數 isinstance()
返回的是 True
則證明 對象 mc_n
是 類 MyClas()
的 實例化對象;如果返回 False
則證明 對象 mc_n
不是 類 MyClas()
的 實例化對象。
到此,我們先小結一下:
類 簡單理解:它就是相當於一個 圖紙,在編寫程序前 彙總 我們的 各種需要,再根據 類 來 創建對象。
所以說:類 就是 對象 的 圖紙。
我們也稱:對象 是 類 的 實例(instance)。
# 創建 類 的語法:
class 類名([父類]):
pass
實例化類 / 引用 類 創建 對象 的方法:
class MyClass():
pass
mc = MyClass()
如果 多個對象 是通過 一個類 創建的,我們稱 這些對象 是:一類對象。
最後是 驗證函數 isinstance()
,驗證 實例化對象 是否是由 類MyClas()
實例化的對象:
class MyClas():
pass
class MyClass():
pass
mc_1 = MyClass()
q = isinstance(mc_1,MyClass)
print(q) # True / False
3. 對象的創建流程
類 和 對象 的簡介大致的我們都已經說完了,接下來呢,我們一起來說說 對象的創建流程。
看到這個內容,有些朋友就會問了:前面在講 類 的時候不是已經說過 對象 是 類 的 實例(instance)。這還有什麼 創建流程 的嗎?還真有。接下來呢,我們就一起的來嘮嘮。
前面我們說了 類 就是 對象 的 圖紙;我們可以根據這個 圖紙 重複的 創建N個對象。你 創造 一臺大衆不夠,我還要 創造 一臺奧迪、 創造 一臺寶馬……我們 創造 了這麼多的東西,舉了這麼多的案例,我們現在再回頭看看:我們今天一直說的這個 類 到底是幹嘛的?
其實 類 也是 一個 對象,類 就是 用來 創建對象 的 對象。(這句話很重要!!!)
換個理解說法:類 這個 對象 的 主要功能 或者說 唯一功能 就是:創建對象。
上面我們講過:一個 對象 裏要有 id、type 和 value。
舉個例子 現在有一個 對象 a = 1
,這個 對象 在我們的 計算機內存 裏 開闢 了一個 空間,在這個 新開闢的 內存空間 裏除了存儲 對象a
,還有 對象 在 新開闢的 內存空間 裏的 地址id
,對象 本身的 類型type
,以及 對象 對應的 值value
。
那麼我們說即然一個 對象 裏要有 id、type 和 value;那麼一個 類 裏是不是也要有 id、type 和 value 這三個值啊?一起來看一下:
class MyClass():
pass
print(id(MyClass),type(MyClass)) # 2855264727088 <class 'type'>
看上面實例的返回值:第一個2855264727088
,我想讀者朋友你應該沒什麼問題,這就是這個 對象 在 計算機內存裏新開闢的 內存空間 的 地址id
,這個還是很簡潔明瞭,很好理解的;可是後面返回的結果<class 'type'>
就有些不明白了,MyClass
它的 類型 是 'type'
?這個怎麼理解?這返回值的意思是不是說明了:類 是'type'
類型的一個對象;定義 類 實際上是定義了一個'type'
類型的 對象。
看到這,大家可能已經有點懵了。這繞來繞去怎麼像是 繞口令 似的~其實也沒那麼的複雜,我來解釋一下這在 “定義一個 類” 的過程中究竟都發生了哪些事。
參考實例:
class MyClass():
pass
mc = MyClass()
通過拆解上面的 參考實例 我們得到以下的 思維導圖:
通過 導圖 我們發現:我們首先呢創建了一張 圖紙 叫做MyClass
,這是我們 自定義類,我們把它呢存放在一個 變量 裏,寄存的變量 你叫它MyClass
也行,叫它ABC
都可以; 變量MyClass
的內存地址值爲0x123
,前面說過:只要是 對象,就一定會在 計算機的內存 裏開闢一個專屬於某一變量的 內存空間。那麼 變量MyClass
的 內存空間 裏是否就有 id:0x123
、type:class 'type'
(只不過 自定義類MyClass
的 類型 有點 特殊) 和 value(現在暫時不講它的內容) 這幾個基礎信息。
接下來呢,我們又寫了另外的一行代碼:mc = MyClass()
;通過 變量mc
實例化了我們 自定義 的這個 對象 MyClass()
形成的一個 新的對象,既然是一個 新的對象 那也就有它自己專屬的 內存空間,既 id:0x223
。那麼 變量mc
的 內存空間 裏是否有 id:0x223
、type:MyClass
和 value(現在暫時不講它的內容) 這幾個基礎信息;只不過呢,此時我們實例化的對象mc
它的屬性 type 是 MyClass
這個 類對象,而不是 自定義類 MyClass
的 類型class 'type'
。
到此呢,我們算是把 類 以及它是如何 實例化,還有 實例化 後的 對象 和 原本的 類 到底有什麼區別 算是解釋清楚了。那麼我們接着往下看。
現在我們發現通過這個MyClass
這個 自定義類 創建的 對象,但它三個 基礎屬性 裏的 value
是不是 沒有值 啊?是不是?也就是說:這個 對象 的裏面實際上是 什麼都沒有 的對吧,就相當於是一個 空的盒子。那麼,此時,就會有讀者疑問了,你們一定會問:小編,這個 空的盒子 它有什麼用啊?那麼此時,小編我就也要問你一句了:既然是 空的盒子,那我們是不是就可以往裏面 添加東西 了?😃
那這個 添加東西 要怎麼 添加 呢?我們先來講解語法:
首先呢,我們要先定義 添加東西 的操作的意義:我們可以向 對象 中 添加變量,對象 中 添加的變量 我們稱之爲 屬性。 語法:對象.屬性名 = 屬性值
只看這些抽象的文字,大家肯定也是看不懂,接下來跟着小編我的 參考實例 一起來理解,上面這段話究竟是什麼意思。
參考實例:
回到前面我們創建的 自定義類對象MyClass
和 實例化對象mc
class MyClass:
pass
mc =MyClass()
此時,我們添加一行代碼mc.name = '蜘蛛俠'
,即:
class MyClass:
pass
mc = MyClass()
mc.name = '蜘蛛俠'
運行後我們會發現:最後什麼都沒輸出。什麼都沒有?不過沒關係,問題等等說,對於 勵志未來做一個 優秀程序員 的我們來說:我們當下編輯的代碼,只要 沒報錯 就是最大的 進步。在此,給我們彼此一個掌聲~ 😃 閒言少敘,那我們來自問自答一句:我們添加的代碼mc.name = '蜘蛛俠'
是在做無用功嗎?肯定不是的了。來看 參考實例:
class MyClass:
pass
mc = MyClass()
mc.name = '蜘蛛俠'
print(mc.name) # 蜘蛛俠
通過參考實例我們發現:代碼mc.name = '蜘蛛俠'
的這個操作的意思是 我們向 實例化對象mc
的 value 裏添加了一個對象 name = '蜘蛛俠'
。
至於如何查看我們的 添加 是否成功,我們只需要通過print( )
打印以下這個新添加的 對象 就可以確定了。
現在,我們已經知道如何的向 實例化對象 的 value 裏添加 對象屬性,也知道如何的 驗證 我們向 value 裏添加的 對象屬性 是否完成。那麼又有一個問來了:現在我們的 自定義類對象MyClass
實例化 了兩個 實例化對象,分別是 實例化對象mc
和 實例化對象mc_1
,通過上面的 案例 已知我們向 實例化對象mc
裏添加了一個屬性mc.name = '蜘蛛俠'
,不過,此時我們如果打印 實例化對象mc_1
的 value 裏的name
屬性,最後會是什麼結果呢?即 print(mc_1.name)
。看以下 參考實例:
class MyClass:
pass
mc = MyClass()
mc_1 = MyClass()
mc.name = '蜘蛛俠'
print(mc_1.name) # AttributeError: 'MyClass' object has no attribute 'name'
AttributeError: 'MyClass' object has no attribute 'name'
翻譯一下:屬性錯誤:對象'MyClass'
裏不存在名叫'name'
的屬性。那麼,我們來解釋一下,系統爲什麼會反饋回這個錯誤:此時在內存空間中多出了一個名爲mc_1
的變量,此時我們也假設這個變量開闢的 內存空間 的地址值爲0x323
。此時,我們發現:新的 實例化對象mc_1
只是實例化了我們的 自定義類對象MyClass
;而代碼mc.name = '蜘蛛俠'
的這個操作的意思是向 實例化對象mc
裏添加一個 name = '蜘蛛俠'
的 屬性,此操作和 自定義類對象MyClass
沒任何關係。所以,我們打印 實例化對象mc_1
的 value 裏的name
屬性,自然也就會反饋 屬性錯誤 這個提示了。
此時,如果有讀者朋友們說:我就是想要向 實例化對象mc_1
添加一個屬性怎麼辦呢?自然是:想添加就添加咯。再看 參考實例:
class MyClass:
pass
mc = MyClass()
mc.name = '蜘蛛俠'
mc_1 = MyClass()
mc_1.name = '鋼鐵俠'
print(mc.name) # 蜘蛛俠
print(mc_1.name) # 鋼鐵俠
到此,我們先小結一下:
前面我們小結過了 實例化類 / 引用 類 創建 對象 的方法:
class MyClass():
pass
mc = MyClass()
這一塊呢,我們主要講了:1) 什麼是實例化對象;2) 整個自定義對象實例化的創建流程;以及呢 3) 如何向實例化對象中添加屬性 value 的 值:
class MyClass():
pass
mc = MyClass()
mc.name = '蜘蛛俠'
print(mc.name) # 蜘蛛俠
4. 類的定義
自古:沒有規矩,不成方圓。曾幾何時,我最好的朋友 L先生 和我講過一段話,讓我對 整個計算機的認識 產生了 醍醐灌頂 的 升維,也讓我在後來自學編程的時候有 降維打擊 的暢通感;那他和我說了什麼呢?
L先生 問我:你認爲 計算機 或者說 編程 它 真正的核心 是什麼?是 語法?是 語言?是 算法?是 數學?雖然它們都有或多或少的關係,但是 也只是 有點關係。計算機 真正的核心是 管理。你沒有聽錯,就是 管理。管理 這個詞在 普通人 的理解裏就是:上級管理下級,讓他們好好幹活,別出幺蛾子、別犯錯;管理 這個詞在 管理高手 的理解裏就是:場控;瞭解管理 市場、瞭解管理 客戶、瞭解管理 產品、瞭解管理 公司、瞭解管理 行政、瞭解管理 供需、瞭解管理 員工……通過 不同的管理方法 管理着 各個對象 的 平衡,讓他們彼此之間 和諧良性 的 交互運轉,彼此 共同的平穩增長,直至達成我們最終的目標。你看,這和 計算機原理 是不是 一模一樣 的。瞭解管理 每個代碼、瞭解管理 語言語法、瞭解管理 前端後端、瞭解管理 不同框架、瞭解管理 交互運行、瞭解管理 環境變化、瞭解管理 訪問運行、瞭解管理 數據變化……通過對 不同模塊運轉 的 管理 使 各個對象 運轉達到 一定的平衡,讓他們彼此之間 和諧良性 的 交互運轉,彼此 共同的平穩增長,直至達成我們最終的目標。再把各個 限制、條件、規則、原理、因果 再 細分、拆解、拼裝,你看“計算機”真正的核心法則是不是就這些。
在講接下來的一塊前我沒什麼要用這麼大的一段這麼隆重的開場白呢?因爲我們接下來要講“計算機”真正的核心法則之一: Python語言 的 類的定義。
OK,閒言少敘,即然我們要開始講 Python語言 的 類的定義,那麼就把我們先前的 自定義類MyClass
拿過來吧。
參考實例:
class MyClass:
pass
mc = MyClass()
mc.name = '蜘蛛俠'
mc_1 = MyClass()
mc_1.name = '鋼鐵俠'
print(mc.name) # 蜘蛛俠
print(mc_1.name) # 鋼鐵俠
實例 看完了,結果 呢也知道了,此時我們是不是該問一問:一頓操作看着虎,你這操作是在幹什麼呀? 或者說:我們添加的這個name
有什麼用?
其實,我們這麼添加的name
沒什麼用,而且這個 操作 也 不太對。爲什麼 不太對?這最後的結果不是並沒有 報錯 嗎。
我們現在呢來詳細的看一看:首先,我們先來看看這個 MyClass()
,這個MyClass()
是什麼,是不是我們 自定的義類 啊;我們前面說過這個 MyClass()
相當於是一個 盒子,雖然這個 盒子 裏現在啥都沒有,現在通過 MyClass()
創建的 對象 也是一個 空對象;但是呢,我們也不要小瞧了這個 空盒子,因爲它是 萬能 的。哪它是怎麼個 萬能 法呢?既然是個 空盒子 當然就是你想放什麼就放什麼;比如:name、age、title……等等等等,但是呢 代碼mc.name = '蜘蛛俠'
和 代碼mc_1.name = '鋼鐵俠'
這兩個操作都是向 對象 裏添加了 一個屬性;我們是不是可以往這個容器裏多放一些。
前面我們說過:類這個容器 相當於一個 圖紙,那麼既然是 圖紙 那是不是就要有它的 規矩 啊。既不能 什麼都不放,也不能 肆意的添加 屬性 和 元素。
既然,我們說 圖紙 要有它的 規矩,那麼 我們接下來就來講一個 概念:類 和 對象 都是對 現實生活中事物 的 抽象。
怎麼 理解 這個 概念,或者說 我們要怎麼 抽象 呢?舉個例子 :假設,我現在想對一個人 抽象,那我們該怎麼 抽象 呢?或者說 我們該怎麼通過 一個類 或者 一個對象 來 描述 一個人。所謂的 抽象 不是說把什麼東西 拓印 出來一個,而是把 某一事物整個 的 特點 和 屬性 放到 程序裏的一個容器 當中。
所以,我們先來簡單的總結一下:所有實際的事物都是由 兩部分 組成:
1. 數據(屬性);
2. 行爲(方法);
我們先來說這第一部分 數據(屬性):人 有 年齡、身高、體重、三維、多少骨頭、幾個牙、幾個眼睛、幾個嘴巴、幾處受傷……這些是不是都是 一個人 的 數據;一個人 除了 數據 還有什麼?人 是不是還 可以跑、可以打架、可以罵人 等等,你可以做 很多很多 好的事情,也可以做 很多很多 不好的事情;這些是什麼?是不是都是 一個人 的 行爲(方法)。
和大家磨嘰了這麼多,接下來給大家整個案例來換換腦子:
參考實例: 嘗試定義一個人類
class Person():
pass
p_1 = Person()
p_2 = Person()
我們 自定義 了一個 類Person
,並 實例化 了兩個對象,分別是 p_1
和 p_2
。這兩個 人 在 程序 裏呈現的形式在這裏我們就不再重複了。
這裏呢,我給大家畫了一個簡單的 思維導圖。如果 不太清楚 這兩個 對象 在 程序 裏呈現的形式,回到上面的 3. 對象的創建流程,把這兩個 對象 代入到我爲你畫的 思維導圖 裏再仔細的思考一下,在這我們就不浪費大家的閱讀空間了。
前面我們說過,現在的兩個 實例化對象 p_1
和 p_2
是兩個 空對象,或者說是兩個 空盒子,兩個 對象 裏面現在啥東西都沒有;雖然我們前面介紹過 可以通過 實例類名.屬性 = 'xxxxx'
向 實例對象 裏添加 屬性,但是沒啥用。爲什麼沒啥用? 因爲我們對 自定義 的這個 類 沒什麼約束,自然了 我們向這個 類 裏添加的 屬性 自然的也就也沒什麼意義。創建一個 對象,這個 對象 裏如果什麼 屬性 都可以添加的話,那 創建的這個 對象 也就涼了,它就沒什麼意義了。此時,我們是不是就要問一句:怎麼辦? 我們 怎麼做 才能讓我們 創建的這個 對象 有意義呢?
在我們自己 自定義類 的時候是不是說過:類 裏面有個東西,它叫做 代碼塊。此時,我們 自定義的類Person()
裏代替了 代碼塊 的是不是 代碼pass
啊。接下來我們就要再說一個 類的定義 的 概念 了,那就是:在 類 的 代碼塊 中,我們可以 定義 變量 和 函數;在 類 中我們定義的 變量 將會成爲 所有實例對象 的 公共屬性,所有實例對象 都可以訪問這些 屬性。
參考實例: 向 自定義類 Person
裏添加兩個屬性,分別是 a = 10
和 b = 20
。
class Person():
a = 10 # 向 自定義類Person() 裏添加一個屬性 a = 10
b = 20 # 向 自定義類Person() 裏添加一個屬性 b = 20
p_1 = Person()
p_2 = Person()
按照我們剛剛說的 類的定義 的 概念,通過 自定義類Person
實例化 的兩個對象 p_1
和 p_2
是不是就可以訪問 類 裏的兩個屬性了?閒言少敘,直接來 打印一下:
class Person():
a = 10 # 向 自定義類Person() 裏添加一個屬性 a = 10
b = 20 # 向 自定義類Person() 裏添加一個屬性 b = 20
p_1 = Person()
p_2 = Person()
print(p_1.a, p_2.b) # 10 20
通過最後 打印出來的結果 我們 驗證 了 在 類 的 代碼塊 中,我們可以 定義 變量 和 函數;在 類 中我們定義的 變量 將會成爲 所有實例對象 的 公共屬性,所有實例對象 都可以訪問這些 屬性 這個定義。
那麼我們接着往下看:前面說了,我們這個 自定義類Person
是一個 人類,但是我們剛剛定義的這兩個變量 a = 10
和 b = 20
在實際的 Person()
操作中是不是並沒有什麼 意義 啊?在本塊內容的開始時我們說過:我們 自定義類Person
要有 人類 共同的 特點 或者是 特徵。所以,我們在 自定義類 的 代碼塊 裏要儘可能多的定義一些 有意義 的 屬性 和 方法。那什麼樣的 屬性 和 方法 纔是 有意義 的 屬性 和 方法 呢?再簡化一下問題:在我們 自定義類Person
定義哪些 屬性 和 方法 纔是 有意義 的 屬性 和 方法 呢?一個 人,他是最起碼的是不是要有一個 名字 啊。
參考實例: 向 自定義類 Person
裏添加一個 name
屬性。
class Person():
name = '鋼鐵俠'
p_1 = Person()
print(p_1.name) # 鋼鐵俠
雖然我們最後 打印的返回值 和上一個 參考實例 最後 打印的返回 的結果差不多,但是,你有沒有發現,我們通過這種方式最後 打印出來的結果 是不是比 p_1.name = '鋼鐵俠'
這種通過 實例化類p_1
傳輸進去的屬性信息要 方便、安全 而且還 好用 很多。
這裏呢,我給大家畫了一個簡單的 思維導圖。以畫圖的方式向大家 簡明扼要 的表達一下 參考實例 的小程序在整個的計算機內存中的 演變過程。
回到我們 最初 的總結:所有實際的事物都是由 兩部分 組成:
1. 數據(屬性);
2. 行爲(方法);
剛剛我們介紹完了 自定義類Person
的第一部分 數據(屬性) 的 創建 和 訪問,接下來我們再來講講 自定義類Person
的第二部分 行爲(方法) 的 創建 和 訪問。
我們說了 人 除了有 年齡、身高、體重、三維、多少骨頭、幾個牙、幾個眼睛、幾個嘴巴、幾處受傷……這些,一個 人 是不是還 可以跑、可以打架、可以罵人 等等,你可以做 很多很多 好的事情,也可以做 很多很多 不好的事情;那 一個人 的這些 行爲(方法) 我們又該怎麼 創建 和 訪問 呢。
閒言少敘,我們接下往下看 參考實例:
class Person():
a = 10 # 向 自定義類Person() 裏添加一個屬性 a = 10
b = 20 # 向 自定義類Person() 裏添加一個屬性 b = 20
name = '鋼鐵俠' # 向 自定義類Person() 裏添加一個屬性 name = '鋼鐵俠'
def speak(): # 向 自定義類Person() 裏添加一個方法 speak()
print('Hi~')
p_1 = Person()
p_2 = Person()
print(p_1.name) # 鋼鐵俠
p_2.speak() # TypeError: speak() takes 0 positional arguments but 1 was given
咦,怎麼報錯了?我們先不看代碼,我們先來看看這個 錯誤提示 TypeError: speak() takes 0 positional arguments but 1 was given
是什麼意思。我們把這個 錯誤提示 翻譯成中文,大致意思就是:對象錯誤:調用函數speak
時沒有在 () 裏傳遞進 位置參數 但是 在調用函數時最少要傳遞一個 位置參數。用大白話講就是:你在 調用 我的時候什麼信息都 沒傳,你要給我 傳遞一個參數。
在我們前面學習 函數 的時候知道:當我們 創建的函數後面 定義幾個 形參,我們 調用 的時候就要傳遞幾個 實參;如果我們在 定義函數 的時候沒有定義 形參 的話,那我們在 調用該函數 的時候是不是就不用 再傳遞 進什麼 實參 了。但此時我們調用 自定義類Person
裏面的 方法函數 時,自定義類Person
裏面的 方法函數 裏明明沒有定義 形參,可是 系統 現在需要我們傳遞進一個 實參,不然就給 報錯提示;這不就 矛盾 了嗎。
爲什麼會出現這個問題呢,等會呢我們在 5. 參數 self 裏再詳細的講解,現在我們先嚐試着向 自定義類Person
裏創建 方法函數speak()
裏添加一個 參數,然後我們再試着調用一下 p_2.speak(a)
,看下這最後的打印結果:
class Person():
a = 10 # 向 自定義類Person() 裏添加一個屬性 a = 10
b = 20 # 向 自定義類Person() 裏添加一個屬性 b = 20
name = '鋼鐵俠' # 向 自定義類Person() 裏添加一個屬性 name = '鋼鐵俠'
def speak(a): # 向 自定義類Person() 裏添加一個方法 speak()
print('Hi~')
p_1 = Person()
p_2 = Person()
print(p_1.name) # 鋼鐵俠
p_2.speak(a) # NameError: name 'a' is not defined
咦,怎麼還報錯?我 最愛的讀者朋友,你先消消氣,先穩定一下 情緒。我們還是先不看代碼,我們先來看看這個 錯誤提示 NameError: name 'a' is not defined
是什麼意思。我們把這個 錯誤提示 翻譯成中文,大致意思就是:名稱錯誤:內有找到名字叫 'a'
的對象。
沒有 實參 的調用沒有定義 形參 的 自定義類Person
裏創建 方法函數speak()
結果報錯;添加了 實參 調用定義了 形參a
的 自定義類Person
裏創建 方法函數speak(a)
結果也是報錯;這,這,這……這在正常情況,我們都是要心態爆炸的了。但是,編程就是個 手藝 + 經驗 的活,首先我們要保持自身的心態平衡。一次、兩次的嘗試 都還 出錯,這隻能證明 我們嘗試的次數還是不夠。廢話不多說,我們現在就開始下一種方法的嘗試。
參考實例:
class Person():
a = 10 # 向 自定義類Person() 裏添加一個屬性 a = 10
b = 20 # 向 自定義類Person() 裏添加一個屬性 b = 20
name = '鋼鐵俠' # 向 自定義類Person() 裏添加一個屬性 name = '鋼鐵俠'
def speak(): # 向 自定義類Person() 裏添加一個方法 speak()
print('Hi~')
p_1 = Person()
p_2 = Person()
print(p_1.name) # 鋼鐵俠
p_2.speak(a) # NameError: name 'a' is not defined
還是 報了 NameError
錯誤;沒事,兩個 () 最多最多我們也就嘗試 四次 就能得到所有結果了。
class Person():
a = 10 # 向 自定義類Person() 裏添加一個屬性 a = 10
b = 20 # 向 自定義類Person() 裏添加一個屬性 b = 20
name = '鋼鐵俠' # 向 自定義類Person() 裏添加一個屬性 name = '鋼鐵俠'
def speak(a): # 向 自定義類Person() 裏添加一個方法 speak()
print('Hi~')
p_1 = Person()
p_2 = Person()
print(p_1.name) # 鋼鐵俠
p_2.speak() # Hi~
咦,成功了?這是不是有點 不合常理 哦。最初我們在調用 自定義類Person
裏創建 方法函數speak()
出現錯誤時,我們就複習了一下前面的我們學的有關於 函數 的知識,我們知道:當我們 創建的函數後面 定義幾個 形參,我們 調用 的時候就要傳遞幾個 實參;如果我們在 定義函數 的時候沒有定義 形參 的話,那我們在 調用該函數 的時候是不是就不用 再傳遞 進什麼 實參 了。可是現在的情況卻是:想要正常的調用 自定義類Person
裏面的 方法函數,我們就需要定義好 自定義類Person
裏面的 方法函數 裏的 形參,而我們在調用 自定義類Person
裏面的 方法函數 時卻不需要傳遞進 實參。這又是什麼情況?這是不是也太反常裏了。
這個 反常理 的問題呢,我們在後面的 5. 參數self 着重的再解釋一下。這裏呢我們先把整個 4. 類的定義 先介紹完,馬上就要結束了。
我們調用 自定義類 裏面的 方法函數 的語法:對象.方法名()
。
方法調用 和 函數調用 的 區別:
如果是 函數調用,調用函數時 有幾個形參,就會傳遞 幾個實參;
如果是 方法調用,自定義類 裏 默認 傳遞進 一個參數,所以 方法 中 至少 得有 一個形參。
到此呢,有關 4. 類的定義 小編我算是簡單的介紹完了,接下來我們簡單的小結一下:
- 類 和 對象 都是對 現實生活中事物 的 抽象。所謂的 抽象 不是說把什麼東西 拓印 出來一個,而是把 某一事物整個 的 特點 和 屬性 放到 程序裏的一個容器 當中。
- 所有實際的事物都是由 兩部分 組成,分別是:1) 數據(屬性) 和 2) 行爲(方法);
- 在 自定義類 的 代碼塊 中,我們 可以定義 變量 和 函數。
變量 會成爲該 自定義類 實例 的 公共屬性,所有由該 自定義類 實例化的對象 都可以通過print(對象.變量)
的形式訪問;
函數 會成爲該 自定義類 實例 的 公共方法,所有由該 自定義類 實例化的對象 都可以通過對象.方法名()
的形式訪問。 - 方法調用 和 函數調用 的 區別:
如果是 函數調用,調用函數時 有幾個形參,就會傳遞 幾個實參;
如果是 方法調用,自定義類 裏 默認 傳遞進 一個參數,所以 方法 中 至少 得有 一個形參。
5. 類中的屬性和方法
對於 如何 自定義類 以及這 自定義類 有什麼 作用,我們已經很詳細的講解過了。接下來呢 我們就要仔細的講解 類 中的 屬性 和 方法 的調用。
參考實例:
class Person():
a = 10 # 向 自定義類Person() 裏添加一個屬性 a = 10
b = 20 # 向 自定義類Person() 裏添加一個屬性 b = 20
name = '鋼鐵俠' # 向 自定義類Person() 裏添加一個屬性 name = '鋼鐵俠'
def speak(a): # 向 自定義類Person() 裏添加一個方法 speak()
print('Hi~')
p_1 = Person()
p_2 = Person()
print(p_1.name) # 鋼鐵俠
p_2.speak() # Hi~
我們 自定義 的這個 類,不管是這 自定義類 裏面的 屬性 還是它裏面的 方法 我們都是放在這個 類對象 當中;而且呢,在我們後面的 實例對象 p_1
和 p_2
當中,都沒有任何的一個 屬性 或者 方法 的。
在講 類 和 對象 的時候,我們知道:所有的 實例對象 都可以訪問 被實例 的 類 裏的 任何的一個 屬性 或者 方法。此時,我們就要問一個問題了:這個 實例對象 爲什麼能訪問到我們 自定義 的這個 類 裏的 屬性 或者 方法 呢?這是爲什麼呀?
看完 問題 後,就有讀者朋友就會說了:這個 實例對象 本身就是根據我們 自定義 的這個 類 造出來 的,能訪問到 自定義類 裏的 屬性 或者 方法 這不是正常的嗎。
在開始回答大家這個問題之前呢,我們首先要從 根 上明確一個問題,那就是:類 中 定義的 屬性 和 方法 都是 公共的,任何該 類 的 實例 都 可以訪問。明確這個 根 問題之後呢,我們接下來就開始來聊聊上面的 爲什麼?
在說這 爲什麼 之前呢,我們要先了解 自定義類 裏的 屬性 和 方法 的 查找流程。這個 屬性 和 方法 的 查找流程 又該怎麼體現呢?
閒言少敘,先拉一個 參考實例:
class Person():
name = '鋼鐵俠'
p_1 = Person()
print(p_1.name) # 鋼鐵俠
單看 代碼 也看不出什麼所以然,我們再拉一個 基於 這段 小代碼 在我們 計算機的 內存 當中運轉的 思維導圖:
通過 參考實例 和 思維導圖 我們發現:雖然 參考實例 最後的 print(p_1.name)
打印出了 鋼鐵俠
,但是我們通過看 思維導圖 發現 實例化對象 的 value
裏其實 什麼都沒有。
那如果,我現在想往 實例化對象p_1
的 value
裏添加些 屬性 我們該怎麼操作?是不是 對象.屬性名 = 'xxx'
就可以了。
參考實例:
class Person():
name = '鋼鐵俠'
p_1 = Person()
p_1.name = '蜘蛛俠'
print(p_1.name) # 蜘蛛俠
基於 參考實例 的 思維導圖:
通過 參考實例 和 思維導圖 我們發現:參考實例 最後打印p_1.name
的結果是 蜘蛛俠
。咦,怎麼和最初 參考實例 的結果不一樣啊?這 參考實例 最後打印 print(p_1.name)
究竟是怎麼一個情況呢?
其實呢 我們打印的 p_1.name
,程序(Python解析器) 會 優先 就近 去 自己的對象當中 找 屬性相對應的 值,如果 自己的對象當中 存在 相對應的屬性 則 直接返回 相對應的 值;如果 自己的對象當中 沒有 相對應的屬性值,程序則會再去我們 自定義的 類 中找 相對應的屬性值;
class Person():
age = '28'
p_1 = Person()
p_1.name = '蜘蛛俠'
print(p_1.age) # 28
如果在 自定義類 中再找不到 相對應的屬性,系統則會返回 AttributeError: 'Person' object has no attribute '……'
錯誤提示。
class Person():
age = '28'
p_1 = Person()
p_1.name = '蜘蛛俠'
print(p_1.title) # AttributeError: 'Person' object has no attribute 'title'
講到這,我們是不是又 間接 的回顧了一遍我們之前說的 代碼優先級;5.1 類的屬性和方法 的調用 和我們之前講的 算術優先級 、 函數優先級 以及 高級函數 以及 函數的作用域 是不是很相似。😃
講完了 類 的 屬性 和 方法 的 查找優先級 流程之後,新的一個問題就 又來了。是 什麼問題呢?那就是:我們什麼時候應該把 屬性 放在 類對象 當中?什麼時候又應該把 屬性 放在 實例對象 當中?這是一個 在未來 我們 經常會遇到 的重要問題。
那這 屬性 和 方法 我們該怎麼存放呀?我們先來看一個問題:一個人 他 最少也必須 要有一個名字吧,而且還是 每個人 都是 最少也必須 要有一個名字吧;但是,每個人 的 名字 在不考慮 重名 的基礎前提上,他們又都是 不一樣 的 對吧。張三、李四、空、大仙、。、Tor、Tol、アリバ、サイト、한국어、Чернобыль ……不僅不一樣,而且還可能是 各種各樣 的 對吧。
介紹完了一種 人類 必須有但又必須都不相同的 屬性,接下來我們來看那些 人類 必須有且都相同的 屬性:在 排除所有特殊問題 的基礎上,每個人 他都會 睡覺、說話、行走、喫飯、喝水、喜歡 …… 等等的這些 日常行爲操作 對吧。
講到這,我想大家隱隱的能感覺接下來我會講什麼了。那就是:
類對象 和 實例對象 中 都可以保存 屬性 和 方法。
如果這個 屬性 或 方法 是 所以實例 共享 的,則應該將其保存到 類對象 中;
如果這個 屬性 或 方法 是 某個實例所 獨有 的,則應該將其保存到 實例對象 中。
至於這 屬性 或 方法 的添加方法,在這裏就不再重新的詳細描述了;對象.屬性 = 'xxx'
和 對象.方法名()'
。
一般情況下,屬性 保存到 實例對象 中,實例對象.屬性 = 'xxx'
;而 方法 需要保存在 類對象 中。
請注意:我們在這裏講的 屬性 和 方法 的 保存方法 講的是 在一般常規的情況下。以後,在我們參加工作中,如果出現一些 特殊情況 的話我們還要根據 具體的情況 具體處理。
到此呢,有關 5. 類中的屬性和方法 小編我算是簡單的介紹完了,接下來我們簡單的小結一下:
對於在 實例對象 中,調用 屬性(方法) 的優先級:
當我們調用一個 對象 的 屬性(方法) 時,程序(Python解析器) 會 就近 先在 當前的對象 中尋找是否有 該屬性,如果 有,則直接返回 當前對象 的 屬性值。如果 沒有,則去 當前對象 實例化的類對象 中去尋找,如果 有 則返回 類對象 中的 屬性值。如果再沒有則會返回 AttributeError: 'Person' object has no attribute '……'
錯誤提示。
對於 屬性 和 方法 一般情況下 的 定義思路:
1) 類對象 和 實例對象 中 都可以保存 屬性 和 方法。
2) 如果這個 屬性 或 方法 是 所以實例 共享 的,則應該將其保存到 類對象 中;
3) 如果這個 屬性 或 方法 是 某個實例所 獨有 的,則應該將其保存到 實例對象 中。
4) 一般情況下,屬性 保存到 實例對象 中,實例對象.屬性 = 'xxx'
;而 方法 需要保存在 類對象 中。
6. 參數self
在開始介紹 參數self 之前呢我們先回顧一下在 4. 類的定義 裏留下的一個大問題:
class Person():
a = 10 # 向 自定義類Person() 裏添加一個屬性 a = 10
b = 20 # 向 自定義類Person() 裏添加一個屬性 b = 20
name = '鋼鐵俠' # 向 自定義類Person() 裏添加一個屬性 name = '鋼鐵俠'
def speak(a): # 向 自定義類Person() 裏添加一個方法 speak()
print('Hi~')
p_1 = Person()
p_2 = Person()
print(p_1.name) # 鋼鐵俠
p_2.speak() # Hi~
咦,成功了?這是不是有點 不合常理 哦。最初我們在調用 自定義類Person
裏創建 方法函數speak()
出現錯誤時,我們就複習了一下前面的我們學的有關於 函數 的知識,我們知道:當我們 創建的函數後面 定義幾個 形參,我們 調用 的時候就要傳遞幾個 實參;如果我們在 定義函數 的時候沒有定義 形參 的話,那我們在 調用該函數 的時候是不是就不用 再傳遞 進什麼 實參 了。可是現在的情況卻是:想要正常的調用 自定義類Person
裏面的 方法函數,我們就需要定義好 自定義類Person
裏面的 方法函數 裏的 形參,而我們在調用 自定義類Person
裏面的 方法函數 時卻不需要傳遞進 實參。這又是什麼情況?這是不是也太反常裏了。
接下來呢,我們就一起來好好的研究研究:爲什麼 會出現這樣的情況呢?
參考實例裏的 自定義類 我就不再仔細的介紹了,在 4. 類的定義 裏面已經講爛了。參考實例 中我們先把 代碼def speak(self):
裏的 self
改爲 a
,我們說 函數def speak(a):
裏面有個 參數a
,我們在調用這個 類對象 的 方法函數 的時候,Python語言的解析器 會 默認 的來傳遞這麼一個 形參a
;所以,我們以後寫 類對象 的 方法函數 的時候, 方法函數 的後面至少要定義一個 形參。至於說這個 形參self
有什麼作用,我們現在先別管,到了後面我們會再詳細的解釋的;我們先來看一個 參考實例:
# 讓 p_1.speak() 和 p_2.speak() 最後的輸出結果分別是:'你好,我是蜘蛛俠' 和 '你好,我是綠巨人' 。
class Person():
name = '鋼鐵俠' # 向 自定義類Person() 裏添加一個屬性 name = '鋼鐵俠'
def speak(a): # 向 自定義類Person() 裏添加一個方法 speak()
print('你好')
p_1 = Person()
p_2 = Person()
p_1.name = '蜘蛛俠'
p_2.name = '綠巨人'
p_1.speak() # 你好
p_2.speak() # 你好
通過 參考實例 我們發現,我們的需求是 需要 讓程序的 p_1.speak()
和 p_2.speak()
最後的輸出結果分別是:‘你好,我是蜘蛛俠’ 和 ‘你好,我是綠巨人’ 。單看我們的需求還是很簡單的吧,但是,程序運行後 最後 實際的輸出結果 卻分別只是:你好
和 你好
。
如果我們修改一下 方法函數def speak(a):
裏輸出參數;我們一起來看下最後的輸出結果。
參考實例:
class Person():
name = '鋼鐵俠'
def speak(a):
print('你好,我是蜘蛛俠')
p_1 = Person()
p_2 = Person()
p_1.name = '蜘蛛俠'
p_2.name = '綠巨人'
p_1.speak() # 你好,我是蜘蛛俠
p_2.speak() # 你好,我是蜘蛛俠
通過 參考實例 我們發現,程序運行後 最後 實際的輸出結果 卻分別是:你好,我是蜘蛛俠
和 你好,我是蜘蛛俠
。而且我們還發現:程序最後之所以 只輸出 你好,我是蜘蛛俠
實際原因是因爲我們把 方法函數def speak(a):
最後的輸出結果 寫死了 呀,這麼 寫的代碼 是不是違反了 程序設計的 開閉原則(OCP)。
那假如我們把 方法函數def speak(a):
裏輸出參數再修改一下;我們一起來看下最後的輸出結果。
參考實例:
class Person():
name = '鋼鐵俠'
def speak(a):
print('你好,我是%s'%'綠巨人')
p_1 = Person()
p_2 = Person()
p_1.name = '蜘蛛俠'
p_2.name = '綠巨人'
p_1.speak() # 你好,我是綠巨人
p_2.speak() # 你好,我是綠巨人
通過 參考實例 我們發現,程序運行後 最後 實際的輸出結果 卻分別是:你好,我是綠巨人
和 你好,我是綠巨人
。而且我們還發現:雖然我們把 方法函數def speak(a):
最後的輸出結果使用了 佔位符 進行拆分,其實實際上也是 間接 的把方法函數def speak(a):
最後的輸出結果寫死了 呀。在上一個 參考實例 裏我們說過:這麼 寫的代碼 違反了 程序設計的 開閉原則(OCP)。
既然說,我們的前兩個操作都是直接或者間接的把 方法函數def speak(a):
裏輸出參數 寫死了;那假如我們把 方法函數def speak(a):
裏輸出參數修改爲 print('你好,我是%s'%name)
;我們一起來看下最後的輸出結果。
class Person():
name = '鋼鐵俠'
def speak(a):
print('你好,我是%s'%name)
p_1 = Person()
p_2 = Person()
p_1.name = '蜘蛛俠'
p_2.name = '綠巨人'
p_1.speak()
p_2.speak()
# NameError: name 'name' is not defined
很好,很優秀,很卓越。 這次的修改最後的 輸出結果 直接就 報錯 了 NameError: name 'name' is not defined
。訪問的變量名不存在: 名字爲 ‘名字’ 的變量未定義。
在這裏有個 注意點:參考實例 裏的 自定義類 裏的 方法 它不是個 函數閉包。如果只是一個 函數閉包 是可以訪問到 變量name = '鋼鐵俠'
的,但我們現在是在一個 自定義類 裏對它的 方法 進行操作;自定義類 裏的 方法 是 無法訪問 到 自定義類 裏的 屬性 的。
def Person():
name = '鋼鐵俠'
def speak():
print('你好,我是%s'% name)
return speak
p_1 = Person()
print(p_1()) # 你好,我是鋼鐵俠
# None
很明顯,上述三種操作都無法通過我們的 自定義類Person
來實現需求:讓程序的 p_1.speak()
和 p_2.speak()
最後的 輸出結果 分別是:你好,我是蜘蛛俠
和 你好,我是綠巨人
。那我們該如何正確的使用我們的 自定義類Person
來實現甲方爸爸對我們的需求呢?
其實,拋開一切,我們 只是希望 代碼print('你好,我是%s'% name)
裏的 name
訪問到是p_1.name = '蜘蛛俠'
和 p_2.name = '綠巨人'
。那我們該怎麼操作?
參考實例:
class Person():
name = '鋼鐵俠'
def speak(a):
print('你好,我是%s'% p_1.name)
p_1 = Person()
p_2 = Person()
p_1.name = '蜘蛛俠'
p_2.name = '綠巨人'
p_1.speak() # 你好,我是蜘蛛俠
p_2.speak() # 你好,我是蜘蛛俠
雖然,最後 p_1.speak()
和 p_2.speak()
的 輸出結果 還是一樣的,但是程序 最後的輸出結果 不是通過 寫死輸出結果 來實現的。最起碼,在編碼上我們 符合 了 程序設計的 開閉原則(OCP)。
我們再把 代碼print('你好,我是%s'% p_1.name)
裏的 p_1
改爲 p_2'
。讓我們再來看看最後的 操作結果。
參考實例:
class Person():
name = '鋼鐵俠'
def speak(a):
print('你好,我是%s'% p_2.name)
p_1 = Person()
p_2 = Person()
p_1.name = '蜘蛛俠'
p_2.name = '綠巨人'
p_1.speak() # 你好,我是綠巨人
p_2.speak() # 你好,我是綠巨人
雖然在編碼上我們 符合 了 程序設計的 開閉原則(OCP)。但是程序 最後的輸出結果 還是要通過我們手動對 p_1
和 p_2
進行修改的。所以,如果真的要 計較 的的話,其實上面的 參考實例 還是不符合 程序設計的 開閉原則(OCP) 的。
其實,我們希望 誰調用 最後就輸出 誰的名字,那我們的 代碼 該怎麼編寫才能讓程序 最後的輸出結果 符合我們的需求?我們的 代碼 該怎麼編寫才能讓最終的結果符合 程序設計的 開閉原則(OCP)?
那麼 新的問題 就來了:我怎麼知道 誰在調用 呢?
我們當然是無法知道的了。我們現在只是在我們的 自定義類 裏定義了 def speak(a):
這麼一個 方法函數,我們在定義 方法函數 的時候是 無法確定 最後是誰會 需要調用 該方法的。此時我們看到這個 自定義類 裏的 方法函數 def speak(a):
裏有個 a
,它又能幹什麼?
老方法:遇事不清就 打印。我們現在就先來 打印 這個 a
,看看最後輸出的結果。
class Person():
name = '鋼鐵俠'
def speak(a):
print(a)
p_1 = Person()
p_1.name = '蜘蛛俠'
p_1.speak() # <__main__.Person object at 0x0000027ACC628400>
我們現在 打印 這個 a
是不是隻需要調用 p_1.speak()
把這個 方法輸出就可以了。最後調用輸出的這個結果爲:<__main__.Person object at 0x0000027ACC628400>
這是個什麼呀……<主文件.Person 對象 在內存 0x0000027ACC628400>
。
簡單的講:我們打印的這個 a
最後返回輸出的是一個 對象。
此時,我們再 打印 一下我們的 實例化對象p_1
,看看 打印 它最後會輸出一個什麼結果:
class Person():
name = '鋼鐵俠'
def speak(a):
print(a)
p_1 = Person()
p_1.speak() # <__main__.Person object at 0x0000027ACC628400>
print(p_1) # <__main__.Person object at 0x0000027ACC628400>
咦,怎麼會這樣? 通過最後返回的 打印 結果我們發現:我們對 實例化對象p_1
的 打印結果 和 我們前面 打印 的 自定義類 裏的 方法函數 def speak(a):
裏的形參 a
的 打印結果 竟然完全的一模一樣,連所在的 內存地址值 都一樣。會不會有什麼問題?我們嘗試着再打印一次看看:
class Person():
name = '鋼鐵俠'
def speak(a):
print(a)
p_2 = Person()
p_2.speak() # <__main__.Person object at 0x000002A08C2A8400>
print(p_2) # <__main__.Person object at 0x000002A08C2A8400>
我們再一次看到 實例化對象p_2
的 打印結果 和 我們前面 打印 的 自定義類 裏的 方法函數 def speak(a):
裏的形參 a
的 打印結果 竟然也同時指向一個對象。
如果我們分別通過 實例化對象p_1
和 p_2
打印 自定義類 裏的 方法函數 def speak(a):
裏的形參 a
最後會返回什麼結果呢?打印 一下看看不就知道了。
class Person():
name = '鋼鐵俠'
def speak(a):
print(a)
p_1 = Person()
p_2 = Person()
p_1.speak() # <__main__.Person object at 0x00000270C6B68400>
print(p_1) # <__main__.Person object at 0x00000270C6B68400>
p_2.speak() # <__main__.Person object at 0x00000270C6B8BF70>
print(p_2) # <__main__.Person object at 0x00000270C6B8BF70>
這 打印 的是 同一個 自定義類 裏的 方法函數 def speak(a):
裏的形參 a
爲什麼最後返回的指向的結果還 不一樣了呢?
此時,讀者朋友們你們有沒有發現什麼 規律 啊?此時我們是不是發現:如果 實例化對象p_1
在調用 自定義類 裏的 方法函數 def speak(a):
,那麼 方法函數 def speak(a):
裏的形參 a
是不是就是 p_1
;如果 實例化對象p_2
在調用 自定義類 裏的 方法函數 def speak(a):
,那麼 方法函數 def speak(a):
裏的形參 a
是不是就是 p_2
。
既然發現了這個規律,那麼對 自定義類 裏的 方法函數 def speak(a):
的形參 a
或者說是 形參 self
進行一個 總結:
自定義類 裏的 方法函數 每次 被調用 時,解析器 都會 自動 傳遞一個參數;
傳遞的 第一個參數 就是 調用方法的 本身。
如果是 p_1 調用,第一個參數就是 p_1 對象;
同理,如果是 p_2 調用,第一個參數就是 p_2 對象。
即然現在我總結出了上面這個結論,那麼 回到最初 :對 讓程序的 p_1.speak()
和 p_2.speak()
最後的輸出結果分別是:你好,我是蜘蛛俠
和 你好,我是綠巨人
這個需求,此時我們應該怎麼寫?
參考實例:
# 讓 p_1.speak() 和 p_2.speak() 最後的輸出結果分別是:'你好,我是蜘蛛俠' 和 '你好,我是綠巨人' 。
class Person():
name = '鋼鐵俠'
def speak(self):
print('你好,我是%s'% self.name)
p_1 = Person()
p_2 = Person()
p_1.name = '蜘蛛俠'
p_2.name = '綠巨人'
p_1.speak() # 你好,我是蜘蛛俠
p_2.speak() # 你好,我是綠巨人
最後呢,對於前面的 總結 再做最後一點的 補充:自定義類 裏的 方法函數 的 第一個參數 一般我們習慣把這個參數命名爲 self。
下一次,我們再在別的程序或這項目的類對象中看到 self 時,希望大家別還是 一臉懵 哦。如果擔心自己可能還是會 一臉懵 的話,那就請你 點擊“收藏” 下這篇文章吧。
到此呢,有關 6. 參數 self 小編我算是簡單的介紹完了,接下來我們簡單的小結一下:
1) self 在 定義函數 時 需要定義,但是在 調用 時會 自動傳入;
2) 自定義類 裏的 方法函數 每次 被調用 時,解析器 都會 自動 傳遞一個參數;
3) 傳遞的 第一個參數 就是 調用方法的 本身。
4) self 的 名字 並不是 規定死 的,但是最好還是 按照約定是用 self;
5) self 總是指 調用時 的 類的實例。
總結小便條
本篇文章主要講了以下幾點內容:
- 什麼是 面向對象?
答:爲了方便程序員 快捷的開發 和 方便的維護,以及代碼的 靈活性 和 複用性;我們通過傳統的 面向過程 的編程方式衍化出了 面向對象 的編程方式。
面向對象 的編程方式雖然方便了程序員 快捷的開發 和 方便的維護,也涵蓋了代碼的 靈活性 和 複用性,但是編寫的過程中 不太符合 常規的思維方式,剛開始編寫 的時候相對的會比較麻煩。 - 面向對象 中的 類 到底是什麼?
答: 類 簡單的理解 就是一張 圖紙,在程序中我們需要根據 類 來 創建對象;類 就是對象的 圖紙。自定義類 的 語法:class ClassName:
。
class MyClass():
pass
print(MyClass,type(MyClass)) # <class '__main__.MyClass'> <class 'type'>
- 類 該怎麼使用:
答:通過 自定義變量 將它 實例化。
class MyClass:
pass
mc = MyClass()
mc.name = '蜘蛛俠'
mc_1 = MyClass()
mc_1.name = '鋼鐵俠'
print(mc.name) # 蜘蛛俠
print(mc_1.name) # 蜘蛛俠
-
現實開發中,對 類 在定義時,我們需要注意哪些 細節 和 要素:
答:- 類 和 對象 都是對 現實生活中事物 的 抽象。所謂的 抽象 不是說把什麼東西 拓印 出來一個,而是把 某一事物整個 的 特點 和 屬性 放到 程序裏的一個容器 當中。
- 所有實際的事物都是由 兩部分 組成,分別是:數據(屬性) 和 行爲(方法);
- 在 自定義類 的 代碼塊 中,我們 可以定義 變量 和 函數。
變量 會成爲該 自定義類 實例 的 公共屬性,所有由該 自定義類 實例化的對象 都可以通過print(對象.變量)
的形式訪問;
函數 會成爲該 自定義類 實例 的 公共方法,所有由該 自定義類 實例化的對象 都可以通過對象.方法名()
的形式訪問。 - 方法調用 和 函數調用 的 區別:
如果是 函數調用,調用函數時 有幾個形參,就會傳遞 幾個實參;
如果是 方法調用,自定義類 裏 默認 傳遞進 一個參數,所以 方法 中 至少 得有 一個形參。
-
在 實例對象 中,調用 屬性(方法) 的優先級:
答:當我們調用一個 對象 的 屬性(方法) 時,程序(Python解析器) 會 就近 先在 當前的對象 中尋找是否有 該屬性,如果 有,則直接返回 當前對象 的 屬性值。如果 沒有,則去 當前對象 實例化的類對象 中去尋找,如果 有 則返回 類對象 中的 屬性值。如果再沒有則會返回AttributeError: 'Person' object has no attribute '……'
錯誤提示。 -
屬性 和 方法 一般情況下 的 定義思路:
答:類對象 和 實例對象 中 都可以保存 屬性 和 方法。
如果這個 屬性 或 方法 是 所以實例 共享 的,則應該將其保存到 類對象 中;
如果這個 屬性 或 方法 是 某個實例所 獨有 的,則應該將其保存到 實例對象 中。
一般情況下,屬性 保存到 實例對象 中,實例對象.屬性 = 'xxx'
;而 方法 需要保存在 類對象 中。 -
解釋一下 參數self 的意義。
答:1) self 在 定義函數 時 需要定義,但是在 調用 時會 自動傳入;
2) 自定義類 裏的 方法函數 每次 被調用 時,解析器 都會 自動 傳遞一個參數;
3) 傳遞的 第一個參數 就是 調用方法的 本身。
4) self 的 名字 並不是 規定死 的,但是最好還是 按照約定是用 self;
5) self 總是指 調用時 的 類的實例。
本章回顧暫時就到這了,如果還有點暈,那就把文章裏所有引用的案例代碼再敲幾遍吧。拜拜~