關於 python 中的 self

在 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 屬於裝死不知道,你還是得好好的把東西丟進來說清楚。