標籤:

為什麼Python里類中方法self是顯式的,而C++中this是隱式的?


感覺是個有趣的問題。我之前也覺得,忘了打參數列表前面的 self 的時候很煩人。

在網上查了一下,發現國外對於這個問題的討論也很多。比如這篇文章提到,另外一個人也提出了把 self 變成關鍵詞的建議:

http://neopythonic.blogspot.com/2008/10/why-explicit-self-has-to-stay.html

這篇文章中提到了一些這樣做會產生的問題。此外,Python 的官方文檔中也給出了很多不這樣做的原因:

Design and History FAQ

不過,這些問題其實都是可以解決的。比如說,我們可以讓解釋器自動給參數列表加上 self,就像 C++ 里的 this 一樣不用聲明就可以用。這樣的話其他部分都和之前一樣,不會產生任何問題。

我認為,這個提案不被採納的最重要的原因是:它不符合 Python 追求簡單的設計理念。想像一下,如果這個提案被採納,那麼類函數就不能和其他函數同等對待了,成為了一個特殊情況。這是因為類函數有自動添加的 self,而其他函數沒有。比如說,假如有下面這段代碼,然後讓解釋器自動添加 self 參數:

class C:
def function1(**arg):
pass
function 2 = C.function1

那麼問題來了,參數列表 arg 該不該包含 self?

又如:

class C:
pass

def function2(var):
pass

C.function1 = function2

那麼問題來了,既然 function2 變成了 C 的成員函數 function1,那麼解釋器該不該給 function2 加上 self 參數?

以上兩個問題,都是因為類函數變得特殊而產生的。

另外,如果一個類函數是靜態函數,不需要 self,那麼是不是需要特殊的語法來聲明?於是我們又搞出來了「靜態函數」這樣一個特殊情況。

當然,我提出的這些問題也都是可以解決的,只需要把語法細化一下就可以了。然而,那樣將會使得語法顯得很複雜。而用現在的語法,類函數和普通的函數是一樣的,參數也都是寫出來的那些參數。這樣很 Python。

import this

The Zen of Python, by Tim Peters

Beautiful is better than ugly.

Explicit is better than implicit.

Simple is better than complex.

Complex is better than complicated.

Flat is better than nested.

Sparse is better than dense.

Readability counts.

Special cases aren"t special enough to break the rules.

Although practicality beats purity.

Errors should never pass silently.

Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.

There should be one-- and preferably only one --obvious way to do it.

Although that way may not be obvious at first unless you"re Dutch.

Now is better than never.

Although never is often better than *right* now.

If the implementation is hard to explain, it"s a bad idea.

If the implementation is easy to explain, it may be a good idea.

Namespaces are one honking great idea -- let"s do more of those!


肯定作者個人喜好是主因。但是我覺得不僅僅是巧合,歷史會有一定的必然性,應該和歷史有一定關係。

BS設計C++公認的是受影響於Simula和ALGOL,同時跟名字一樣基於C設計。C語言就沒有關鍵字this(廢話,打死)。對於Simula和ALGOL鄙人才疏學淺,沒接觸過,下面猜測一下,有空再填坑: 一種可能是可能從語法上沒有,或者另一種可能是有self,但是噁心無比,當年搞得BS頭大,畢竟那會的語言很多都噁心人噁心的要死,比如smalltalk的self就很噁心。

另一個很可能的原因在於當時人們曾經一度追求少敲鍵盤。去看看VIM的設計邏輯就知道了:把手指從一個位置移到另一個位置都會覺得罪惡。更何況要多寫6個ascii?還要按shift。如果按照vim的邏輯,這種行為該自殺了已經。

於是BS大師為了這門語言的簡潔,和C++一條基本的設計思想:包容一切可能。所以將this設計為了「可以隱式的」。

但是另一個方向,python作為一門腳本語言,開始並沒有想過那種大規模開發。想想已經到了1995年連java的性能都可能被恥笑,一個解釋性語言在1991年誰敢抱希望他被部署到伺服器承擔幾億的訪問?

既然沒有大規模編程,那麼多敲幾個字母並沒那麼罪惡,而且還不用shift。所以作者完全沒必要提供給用戶「隱式調用self」的功能了。

我覺得自己語言律師和語言考古證快能考了……(暴風雨汗 )


看了一圈發現大家都沒有注意到python的一個特性,python的類可以在運行時動態載入類的方法,也就是把一個函數直接掛載到類里,比如這樣:

可以看到我在外部定義了一個getitem()函數,然後直接讓它成為了Seq類的魔法函數,這樣Seq類就在運行時支持了indexing,很神奇吧

而且我們注意到在掛載之後,getitem(seq, index)其實變成了Seq.__getitem__(self, index),這樣顯式定義的self實際上保證了掛載前後函數定義的一致性。

python果然是世界上最好的語言!


因為 Python 是基於命名空間的面向對象設計啊。

實際上:

instance = ClassName()
instance.method_name()

等同於:

instance = ClassName()
ClassName.method_name(instance)

區別僅僅是,前者是隱式傳遞 self ,後者是顯式傳遞。

而在類中定義的方法,實則可以理解為以類作為命名空間區分的函數。類的創建過程也類似函數的調用過程:

ClassName()
func_name()

不僅僅是形式上一樣,類的創建一定程度上可以理解為一個函數的調用,只不過這個「函數」返回了一個獨立的這個類的「模塊」,既然是類命名空間的模塊自然可以訪問類屬性(例如定義的方法)。而不管是顯式或是隱式傳遞 self,其實都是在指定訪問類屬性的「模塊」。

因為類的「調用」返回的「模塊」是唯一的,而模塊同時也是命名空間,命名空間又可以動態的刪除和添加屬性(這點在包括模塊、類、實例、函數等組件都是相同的,因為它們都是/產生命名空間),所以每個實例自己的屬性又是各自獨立不共享的。

如果你明白了,就知道方法是屬於類這個命名空間的屬性,self.attr 創建的是屬於實例自己命名空間的屬性。

class Person:
def __init__(self, name):
self.name = name # 向 self 這個獨立的命名空間添加屬性:name

def show_name(self): # 給 Person 類這個命名空間定義屬性:show_name
print(self.name)

既然是命名空間,它們當然都可以用 del 刪除,用點屬性名的方式添加和覆蓋:

xm = Person(name="小明")
xh = Person(name="小紅")

delattr(xm, "name") # 刪除實例屬性(對其它實例無影響)

xm.show_name() # 異常:沒有 name 屬性
xh.show_name() # 輸出:小紅

delattr(Student, "show_name") # 刪除類屬性(影響所有實例)
xh.show_name() # 異常:沒有 show_name 屬性

所以某個答案中展示的,能動態給類添加屬性,因為類就是命名空間啊!

————

當然,命名空間能說明一些顯式 self 的原因,但並不表示一定要設計成顯式。


首先,C++中的this不是任何情況都可以省略的,在具有不確定性的情況下顯式使用this可以起到限定作用。

下面比較依一下C++和Python

C++變數使用前必須聲明;python是不需要聲明直接使用的(這一點與其他動態語言也不一樣),這樣就有一個問題,如何判斷一條賦值語句有沒有引入新的變數?C++、Javascript再有在聲明時才會引入新的變數;ruby雖然不需要聲明,但是使用"$@"等符號區分各種變數;而python啥都沒有,無法從一條語句判斷是否引入新變數。

//C++代碼:
class CPP {
public:
CPP()
{
a = 10; //這裡可以確定a就是成員變數
std::cout &<&< "a: " &<&< a &<&< std::endl; //這裡可以確定a就是成員變數 int a = 1000; //引入一個局部變數a std::cout &<&< "a: " &<&< a &<&< std::endl; //這裡的a是定義的局部變數還是成員變數? std::cout &<&< "this-&>a: " &<&< this-&>a &<&< std::endl; //顯式使用this限定a為成員變數 } private: int a; };

運行結果:

a: 10
a: 1000
this-&>a: 10

這裡可以看出來,在沒有不確定性的情況下(第一條賦值、列印語句),this可以省略,在具有不確定性的情況下,this可以起到限定的作用(第三條列印語句),消除不確定性。

再看Python

#Python代碼:
class PYTHON:
def __init__(self):
self.a = 10 #成員變數a
a = 10000 #局部變數

用self很好區分兩個a分別為成員變數和局部變數。如果把self去掉,由系統統一處理隱含的self就會出問題,系統無法確定你想要的是下面哪種情形:

#隱藏self的情形
class PYTHON:
def __init__(self):
a = 10
a = 10000

#下面的情形無法正確選擇

#情況1: 全部添加
class PYTHON1:
def __init__(self):
self.a = 10
self.a = 10000

#情形2: 全部不添加
class PYTHON1:
def __init__(self):
a = 10
a = 10000

情形3:第一個添加
class PYTHON1:
def __init__(self):
self.a = 10
a = 10000

#情形4:第二個添加
class PYTHON1:
def __init__(self):
a = 10
self.a = 10000

由於python的變數在使用前不需要聲明,上面的代碼中兩個變數a系統就無法區分,難作出正確的裁決,這個工作只能通過self顯式限定。

――――――――――――――

感謝

@d41d8c 的提醒,js的this我弄錯了,謝謝


應該就是是語言規定,不過是不是python有什麼限制不能使用隱式的就不了解了,python不熟。


Lua裡面

x:y(...) == x.y(x, ...)

前者只是後者的語法糖

而x.y是這樣聲明的:

x.y = function(self, ...)

Lua的面向對象是基於原型的面向對象,然後最主要的原因是因為Lua裡面的function是first class的。

Python不了解,但是作為腳本語言這個可能性挺大的。


語言規定唄,我剛開始學Python的時候經常犯錯誤不帶self,結果能正確運行但是調不到self(當然咯)。


就是語言風格不同,這個其實跟Javascript不一樣,C++中的類方法有this參數,而且參數還能用const修飾,但是this不用明確寫出來,僅僅如此而已,從語義上和一個額外的參數(雖然調用約定略有差異)沒有什麼不同。

至於理由的話,其中一個理由是C++需要明確寫出參數的類型,this參數的類型就是自己這個類的指針,但是C++可是有模板類的,來,寫一個my_class&,my_c&<&>,new_tmpl,...&> *this試試……

而Python動態語言不用明確寫出類型,寫個self四個字母也不會累死,語法上的一致性很好,類的成員函數和普通函數不需要區分,成員函數和普通屬性也不需要區分,可以更純粹一些。


你定義的 Python 的類裡面的所謂的實例方法不過是一個靜態函數而已。舉個例子:

你能使用 `[].append(1)`,也能使用 list.append([], 1),本質上行為是一致的。

靜態函數的定義是定義在類級別上的,你可以在類的 __dict__ 裡面找到它。在執行實例化類的時候,這個靜態函數會被轉換為 python 閉包,自動傳入第一個參數為該實例而已。

而其它(如 C++/C#/Java)不能主動添加 this 是因為它們的實例方法是「真·實例方法」,是完全不能從類上調用的。

那為什麼 python 的不能是「真·實例方法」呢?因為 python 的所有用戶定義的類都是同一種類,在某種意義上我覺得它傾向於弱類型。

你可以自定義兩個類:

class A: pass

class B:

def m(self): return 1

然後:

a = A()

a.__class__ = B

a.m() // 1


成員函數參數開頭有個self,靜態函數沒有。參數里都有self了,隱式self好像沒邏輯。

作用域的問題,python的作用域是全局的,沒self會衝突的。


因為隱式有二義性。如果允許省略self,那麼類似這樣的代碼:

class Class1:

def __init__():

myVar = 1

這裡myVar到底是一個成員變數呢,還是臨時變數?

Python為了簡潔性省略了變數聲明,不過這個簡潔性也是有代價的,那就是部分情況下可能出現二義性(引入global聲明全局變數也是因為類似的原因)。一般說來,代碼中變數數量遠多於self/this 出現的次數,某些同學不要看到self多了就煩,你要想想你已經省掉了多少定義變數的工作。所以這個語言特性是權衡後的選擇,是利大於弊的。

C++因為所有變數都需要聲明之後才能使用,所以不存在這個問題。

另外,其他動態語言比如Ruby也有這個問題,不過Ruby選擇了用特殊符號@或@@來做區分,這樣寫起來比Python更簡潔一些,但是看起來比較魔幻,不是Python的風格。喜歡哪種風格是程序員的個人口味問題。


謝邀。

完全不了解。


與此同時,另一種顯式 self 的語言 Perl 被人黑出翔...


不用想的這麼高大上,作者自以為是而已

出生就是作為玩具語言,各種蹩腳的語法

當然對於信徒來說,再蠢的語法都覺得優雅,大吹特吹

生態圈和實用性還行,語言設計就呵呵了


程序設計理念在發生變化吧,C++那會兒厭倦了彙編的古板,希望設計出方便酷炫簡潔的語言,像this這種處處出現的東西能隱就隱了,重載什麼的更是玩的溜。

現在靈活的語言如雨後春筍,多如牛毛,以至於反而開始提倡返璞歸真,要求儘可能書寫一致,沒有歧義。這就要求在語法上做諸多限制,為了達到語義清晰。像self就要求顯式寫出。像go這種語言甚至不允許重載,不允許有默認值的參數等等,都是差不多的理念。


其實沒那麼複雜,輸入import this 看一下Python哲學,第二行就是Explicit is better than implicit,對於一個追求簡單的語言,沒有什麼比把話說明白了好


因為在python中,類本身也是一個對象,類方法定義沒有self,只有類實例的方法才有self。

在c++中,類靜態方法用類名::方法定義。


個人覺得c++這樣的做法是為了把c++和c區別出來裝高大上,現在的rust都顯式了,而且不用static來區別this和非this。


因為在class裡面定義的方法其實也是普通函數,和在類外面定義的函數沒什麼兩樣,而python裡面的函數其實是一種對象,既然是對象,那麼就可以給他賦值。比如,一個Foo序列類一開始沒有實現__setitem__方法,就不能改變這個序列類的實例對象的內容,比如foo[0]=1,這樣的賦值語句是不允許的。

由於python可以給對象動態添加方法,所以可以foo.__setitem__ = another_function(seq,key,value)。

回到問題,如果沒有顯式的self佔位,那麼跟外面定義的another_function函數的參數個數就對應不起來了,所以必須要有self佔位。


推薦閱讀:

同一段代碼,為什麼有的編譯器能編譯通過,有的不能?
有什麼很好的軟體是用 Qt 編寫的?
沒有Linux cpp後台開發經驗的應屆生如何找到一份Linux cpp後台開發的工作?
如何實現快速將 64 位二進位(存在字元串里)轉換為十進位?
C++ 如何跨平台判斷操作系統是32位還是64位?

TAG:Python | 編程 | C | CC |