self在區分全域變數/函數和對象中的成員變數/函數十分有用。例如,它提供了一種範圍機制,我個人認為比Ruby的@和@@清晰多了,這可能是習慣使然吧,但它確實和C++、Java中的this很相似。
然而,self總是有令我困擾的地方,我以前在這裡說過—我曾幻想能在Python3中這些能得以改進,然後通常會引發一輪熱議並最終以人們所說的“顯勝於隱”告終。
我在巴西的時候曾和Luciano Ramalho(巴西Python組織的主席)有過一次交談。他讓我明白並非無處不在的self讓我困擾不已,而是參數列表中的self,我想也稱為非pythonic(un-pythonic)。
它是如何使用的
下面是一些簡單的Python代碼,說明了如何使用類。
複製代碼 代碼如下:def f(): pass
a = 1
class C1(object):
a = 2
def m1(self):
print a # Prints '1'
print self.a # Prints '2'
f() # The global version
self.m2() # Must scope other members
def m2(self): pass
obj = C1()
obj.m1()
首先看f()和a,它們都可在全域範圍中調用。類C1被定義成繼承自object,這是定義一個新類的標準流程(我想在Python3中這些會變得更加不明顯)。
注意,m1()和m2()的第一個參數都是self。在Python中,self不是關鍵字。但按照慣例“self”代表當前對象的地址,也就是對象的地址通常是第一個參數。
在類範圍上定義a是建立對象範圍的方式之一。你也可以在a的method裡賦值給self.a,並且第一次運行該語句時就分配了這個域的記憶體空間。但有必要區分兩種版本的a。若在method內部使用a,那麼這個a就是全域版本的,而self.a體現的是對象域(你也可以在類內部對全域變數進行賦值,這裡我暫不考慮這種情況)。
同樣地,一個對f()的非限定調用(unqualified call)造就了全域函數,通過對其限定self.m2()調用的是成員函數(同時將當前對象地址作為傳遞給m2()的self變數)。
現在來看一個含有帶參數的method的類: 複製代碼 代碼如下:class C2(object):
def m2(self, a, b): pass
為了調用該method,我們建立了一個對象執行個體,然後使用點運算式調用對象obj上的m2(): 複製代碼 代碼如下:obj = C2()
obj.m2(1,2)
在調用過程中,obj的地址作為self變數在m2()中隱含傳遞,這裡遇到了一個嚴重的矛盾:為何當定義method時隱式好於顯式,而調用method時隱式也毫無問題?
當然我想這可能是method調用文法所要求的,但這就意味著method的定義和調用有很大不同,這裡既沒有“顯式”也不pythonic。在調用參數個數錯誤的method時就能看出來:
obj.m2(1)
結果錯誤提示為:
Traceback (most recent call last):
File "classes.py", line 9, in <module>
obj.m2(1)
TypeError: m2() takes exactly 3 arguments (2 given)
由於method調用期間self的隱式參數傳遞,上述錯誤資訊實際是說應該這樣調用method:
C2.m2(obj,1,2)
即使上面這行語句運行成功,它當然也不是實際的調用方式。你應該使用常規的method調用文法,即傳遞兩個參數:
obj.m2(1,2)
錯誤資訊“m2() takes exactly 3 arguments (2 given)”不僅讓初學者十分糊塗,我每次看到它後也常常懵住。我想這既表明了它是非Pythonic、也直指method定義和調用的矛盾。
絕望的建議
儘管漫長歷史中儘是絕望,我又有哪些建議呢?
在Python3.1中增加self為關鍵字(有一點更加向後不相容)(或直接使用this來使C++和Java程式員時更容易過渡)。而所有與self有關的已有規則都不變。
唯一的改變是:你不必將self放入method參數列表中。這是唯一隱式的地方,其它都是顯式的—除了依舊不變的method調用。
它實現了method定義和調用的一致性。你可以定義一個與調用時具有相同參數個數的method。當調用method所傳遞參數個數出錯時,錯誤資訊會通知method應含有的實際參數個數,而不是多出一個。
顯式 vs.冗餘
在我再一次聽到“顯勝於隱”之前,讓某件事變得清晰和變得冗餘還是有區別的。我們已有這樣一種語言:它讓你曆經了無數考驗,原因就是起初看起來似乎很好但之後問題卻越來越多。它叫做Java。
如果想讓每一件東西都變為顯式,我們可以使用C或彙編以及其它能夠精確說明和展現機器內部運行細節的語言。
強製程序員將self放入method參數列表與顯式根本不沾邊,它只是強製造成冗餘的做法,也不會增加編程的表達方式(已經知道是一個method了,何必還要在參數列表中加入self來提醒我們呢)。我認為,它是死板的,也是非pythonic。