在 python 物件方法中,如果存取一個 class 或者 instance 中的變數,
那麼在物件方法宣告參數時,就必須要有一個 self 當作第一個參數。
大約長得像下面這樣:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
看到這個例子中的 self 其實與其他語言如: java, c++ 中的 this 是差不多的東西。
兩者都是代表該 instance 自身
其用途就像這個 python 例子中,初始化的物件方法會傳入一個初始變數 name ,打算讓傳入的變數 name 改變物件裡頭的 name 。有沒有發現上面這句很繞口,這是因為 name 變數重名了,因此 self.name 代表該物件自身的 name 讓傳入的參數與物件自身擁有的 name 不至於混淆。
而在 C++ 和 java 中以 this 來代表自身物件,但是 this 並沒有出現任何宣告就能讓編譯器理解現在的 this 是指誰,因此屬於隱含性質的內建物件。不過這對 python 哲學 Explicit is better than implicit.
(明確比不明確要好)。因此 python 更希望讓讀程式碼的人知道 self 的存在,因此要求寫程式的人必須明確的寫上這個函數會傳入 self 物件。
除了 python 自身哲學想要求之外,我還想從兩個方向來談談 self 的產生。
以 C 的方法模擬物件
如果要 C 來模擬簡單的物件導向程式的話,那麼大概會以 struct 來包裝物件中的成員變數,然後在寫幾個函數來專門控制這些成員變數,以下寫一個簡單的例子。
typedef struct Person {
char *name;
int age;
} Person;
void Person_init(Person self, char *name, int age)
{
self.name = name;
self.age = age;
}
很好,現在寫了一個幾乎與上面 python 例子相仿的 C 語言例子,我們用 struct 來包裝 Person 的成員變數來代表 class ,而一個比較明顯的差別就是,python 物件的 function 是直接套在 class Person 裡面的,而這裡則是將變數跟函數分別拆開來寫,然後函數的命名以 Person 開頭來讓讀程式的人了解該函數是專門初始化 Person 的。當然啦,在 C 裡面可以透過函數指標的方式把 init 套進 Person 裡面,但是會複雜到模糊了我想說明的焦點。
那很明顯的可以看到,如果我想要產生物件並初始化我的物件,那麼我必須做出類似像下面這樣的動作:
Person john;
Person_init(john, "John", 18);
產生一個 john 物件,然後透過 Person_init 來初始化我的 john 物件,而當中第一個參數則是明確指出我要初始化的是 john 這個物件。
因此在 python 的 self 其實也是基於這種基礎之下來設計的。
Python 中實際的轉換
我稍微擴充一下原本的 Person 方便我接下來的解說:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def addAge(self, num=1):
# 如果沒有傳入要增加的年齡,預設遞增一歲
self.age += num
那麼我接下來想產生一個 john 物件然後增加他的年齡應該會寫成以下這樣:
john = Person("John", 18)
john.addAge(5)
而當中的 john.addAge(5)
按照直觀的理解其實就是讓 john 用自己增加歲數的方法增加五歲。而在背後的 python 其實是轉成下面的程式碼:
Person.addAge(john, 5)
實際上 addAge 是在 Person 物件之下(在 python 當中,其實 class 也是一種物件,這有待未來解說,所以 all is object 不是 ruby 的專利)
因此呼叫 Person.addAge 方法必須明確的告知是要增加哪個物件裡的年齡,所以 python 物件方法中要求你必須明確的宣告出第一個 self (當然,名字不一定要叫 self ,也能亂取一個像是 this 這樣的名稱),來讓物件方法知道。
在 python 裡實際的呼叫物件方法,也跟 C 語言裡面的簡單模擬物件有異曲同工之妙。第一個參數需要被宣告出是要哪個物件,除了是哲學上的要求,也有部份原因是有這樣的內部轉換。
java 跟 c++ 中的 this ,在 class 這種藍圖中使用 this 比較像是未來任何用到 this 的地方,我都知道是哪個 instance ,就是被 new 給新增出來的這個嘛,何須言明。
而 python 跟 c 屬於裝死不知道,你還是得好好的把東西丟進來說清楚。