作者:追遠·J
鏈接:https://www.zhihu.com/question/46973549/answer/767530541
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
作為典型的面向對象的語言,Python中 類 的定義和使用是不可或缺的一部分知識。對於有面向對象的經驗、對類和實例的概念已經足夠清晰的人,學習Python的這套定義規則不過是語法的遷移。但對新手小白而言,要想相對快速地跨過__init__
這道坎,還是結合一個簡單例子來說比較好。
以創建一個“學生”類為例,最簡單的語句是
class Student():
pass
當然,這樣定義的類沒有包含任何預定義的數據和功能。除了名字叫Student以外,它沒有體現出任何“學生”應該具有的特點。但它是可用的,上述代碼運行過後,通過類似
stu_1 = Student()
這樣的語句,我們可以創建一個“學生”實例,即一個具體的“學生”對象。
通過class
語句定義的類Student
,就好像一個“模具”,它可以定義作為一個學生應該具有的各種特點(這裡暫未具體定義);
而在類名Student
後加圓括號()
,組成一個類似函數調用的形式Student()
,則執行了一個叫做實例化的過程,即根據定義好的規則,創建一個包含具體數據的學生對象(實例)。
為了使用創建的學生實例stu_1
,我們可以繼續為它添加或修改屬性,比如添加一組成績scores
,由三個整數組成:
stu_1.scores = [80, 90, 85]
但這樣明顯存在很多問題,一旦我們需要處理很多學生實例,比如stu_2
, stu_3
, ...
,這樣不但帶來書寫上的麻煩,還容易帶來錯誤,萬一某些地方scores
打錯了,或者干脆忘記了,相應的學生實例就會缺少正確的scores
屬性。更重要的是,這樣的scores
屬性是暴露出來的,它的使用完全被外面控制著,沒有起到“封裝”的效果,既不方便也不靠譜。
一個自然的解決方案是允許我們在執行實例化過程Student()
時傳入一些參數,以方便且正確地初始化/設置一些屬性值,那麼如何定義這種初始化行為呢?答案就是在類內部定義一個__init__
函數。這時,Student
的定義將變成(我們先用一段注釋占著__init__
函數內的位置)。
class Student():
def __init__(self, score1, score2, score3):
# 相關初始化語句
定義__init__
後,執行實例化的過程須變成Student(arg1, arg2, arg3)
,新建的實例本身,連帶其中的參數,會一並傳給__init__
函數自動並執行它。所以__init__
函數的參數列表會在開頭多出一項,它永遠指代新建的那個實例對象,Python語法要求這個參數必須要有,而名稱隨意,習慣上就命為self
。
新建的實例傳給self
後,就可以在__init__
函數內創建並初始化它的屬性了,比如之前的scores
,就可以寫為
class Student():
def __init__(self, score1, score2, score3):
self.scores = [score1, score2, score3]
此時,若再要創建擁有具體成績的學生實例,就只需
stu_1 = Student(80, 90, 85)
此時,stu_1
將已經具有設置好的scores
屬性。並且由於__init__
規定了實例化時的參數,若傳入的參數數目不正確,解釋器可以報錯提醒。你也可以在其內部添加必要的參數檢查,以避免錯誤或不合理的參數傳遞。
在其他方面,__init__
就與普通函數無異了。考慮到新手可能對“函數”也掌握得很模糊,這裡特別指出幾個“無異”之處:
獨立的命名空間,也就是說 函數內新引入的變量均為局部變量,新建的實例對象對這個函數來說也只是通過第一參數self從外部傳入的,故無論設置還是使用它的屬性都得利用self.<屬性名>
。如果將上面的初始化語句寫成scores = [score1, score2, score3]
(少了self.
),
則只是在函數內部創建了一個scores變量,它在函數執行完就會消失,對新建的實例沒有任何影響;
與此對應,self
的屬性名和函數內其他名稱(包括參數)也是不沖突的,所以你可能經常見到類似這種寫法,它正確而且規范。
class Student():
def __init__(self, name, scores):
# 這裡增加了屬性name,並將所有成績作為一個參數scores傳入
# self.name是self的屬性,單獨的name是函數內的局部變量,參數也是局部變量
self.name = name
if len(scores) == 3:
self.scores = scores
else:
self.scores = [0] * 3
從第二參數開始均可設置 變長參數、 默認值等,相應地將允許實例化過程Student()
中靈活地傳入需要數量的參數;
其他……
說到最後,__init__
還是有個特殊之處,那就是它不允許有返回值。如果你的__init__
過於復雜有可能要提前結束的話,使用單獨的return
就好,不要帶返回值。
上面代碼的執行結果如下:
The project foreground and bac