花下貓語:前兩天,我偶然在一個知識星球(劉欣老師的「碼農翻身」)裏看到一篇主題,劉老師表示 Python 的類方法非要帶個 self,而不像其它語言那樣隱藏起來,這讓人很不爽。我對此也有同感。在通過羣聊討論後,我獲知 Guido 曾經專門撰文解釋過這個問題。這篇文章並很差懂,我抽空先翻譯出來了,看看能收到什麼迴應。若是可能的話,後續再另寫文章分析。html
--------------如下爲譯文---------------python
布魯斯·埃克爾(Bruce Eckel)發了篇博文 ,提議從類方法的形參列表中刪除「self」。我將解釋爲何這個提議不能經過。(譯註:Bruce 是《Thinking in Java》、《Thinking in C++》等多本書籍的做者,也是個 Python 開發者。他的文章總結了當年在巴西 Pycon 上的一次討論,主要觀點是在定義類方法時,形參中的「self」是多餘的,並且由它引起的報錯信息具備必定的誤導性。)程序員
Bruce 知道,咱們須要一種方法來區分對實例變量的引用和對其它變量的引用,所以他建議將「self」設爲關鍵字。web
考慮一種典型的類,它有一個方法,例如:jsp
class C:
def meth(self, arg):
self.val = arg
return self.val
複製代碼
跟據 Bruce 的提議,這將變爲:函數
class C:
def meth(arg): # Look ma, no self!
self.val = arg
return self.val
複製代碼
這樣每一個方法會節省 6 個字符。但我不以爲 Bruce 提出這個建議是爲了減小打字。post
我認爲他真正關心的是程序員(可能來自其它語言)所浪費的時間,有時候彷佛不須要指定「self」參數,並且他們偶爾忘記了要加(即便他們十分清楚——習慣是一種強大的力量)。確實,與忘記在實例變量或方法引用以前鍵入「self.」相比,從參數列表中省略「self」,每每會致使很模糊的錯誤消息。ui
也許更糟糕的是(如 Bruce 所述),當正確地聲明瞭方法,可是在調用時的參數數量不對,這時收到的錯誤消息。如 Bruce 給出的如下示例:spa
Traceback (most recent call last):
File "classes.py", line 9, in
obj.m2(1)
TypeError: m2() takes exactly 3 arguments (2 given)
複製代碼
我贊同它是使人困惑的,可是我寧願去解決此錯誤消息,而不是修改語言。翻譯
首先,讓我提出一些與 Bruce 的提議相反的典型論點。
這有一個很好的論據能夠證實,在參數列表中使用顯式的「self」,能夠加強如下兩種調用方法在理論上的等效性。假設「 foo」是「C」的一個實例:
foo.meth(arg) == C.meth(foo, arg)
複製代碼
(譯註:說實話,我沒有理解這個例子的意思。如下僅是我的見解。在類的內部定義方法時,可能會產生幾種不一樣的方法:實例方法 、類方法 和 靜態方法 。它們的做用和行爲是不一樣的,那麼在定義和調用時怎麼作區分呢?Python 約定了一種方式,即在定義時用第一個參數做區分:self 表示實例方法、cls或其它符號 表示類方法……三種方法均可以被類的實例調用,並且看起來如出一轍,如上例的等號左側那樣。這時候就要靠定義時賦予的參數來區分了,像上例等號右側,第一個參數是實例對象,代表此處是個實例方法。)
另外一個論據是,在參數列表中使用顯式的「self」,將一個函數插入一個類,得到動態地修改一個類的能力,建立出相應的一個類方法。
例如,咱們能夠建立一個與上面的「C」徹底等效的類,以下所示:
# Define an empty class:
class C:
pass
# Define a global function:
def meth(myself, arg):
myself.val = arg
return myself.val
# Poke the method into the class:
C.meth = meth
複製代碼
請注意,我將「self」參數重命名爲「myself」,以強調(在語法上)咱們不是在此處定義一個方法(譯註:類外部的是函數 ,即 function,類內部的是方法 ,即 method)。
這樣以後,C 的實例就具備了一個「meth」方法,該方法有一個參數,且功能跟以前的徹底同樣。對於在把方法插入類以前就建立的那些 C 的實例,它甚至也適用。
我想 Bruce 並不特別在乎前述的等效性。我贊成這只是理論上的重要。我能想到的惟一例外是舊式的調用超級方法的習語(idiom)。可是,這個習語很容易出錯(正是因爲須要顯式地傳遞"self"的緣由),這就是爲何在 Python 3000 中,我建議在全部狀況下都使用"super()"的緣由。
Bruce 可能會想到一種使第二個等效例子起做用的方法——在某些狀況下,這種等效性真的很重要。我不知道 Bruce 花了多少時間思考如何實現他的提議,可是我想他正在考慮將一個名爲「self」的額外形參自動地添加到直接地在類內部定義的全部方法的思路(我必須說是「直接地」,以便那些嵌套在方法內部的函數,能免於這種自動操做)。這樣,可使第一個等效例子保持等效。
可是,有一種狀況我認爲 Bruce 不能在不向編譯器中添加某種 ESP 的狀況下解決:裝飾器。 我相信這是 Bruce 的提議的最終敗筆。
當裝飾一個方法時,咱們不知道是否要自動地給它加一個「self」參數:裝飾器能夠將函數變成一個靜態方法(沒有「self」)或一個類方法(有一個有趣的 self,它指向一個類而不是一個實例),或者能夠作一些徹底不一樣的事情(用純 Python 實現「 @classmethod」或「 @staticmethod」的裝飾器是繁瑣的)。除非知道裝飾器的用途,不然沒有其它辦法來肯定是否要賦予正在定義的方法一個隱式的「self」參數。
我拒絕諸如特殊包裝的「 @classmethod」和「 @staticmethod」之類的黑科技。我也認爲除了自檢外,自動地肯定某個方法是類方法(class method)、實例方法(instance method)仍是靜態方法(static method),這不是一個好主意(就像在 Bruce 的文章的評論中,有人建議的那樣):這使得很難僅僅根據方法前的「def」,來決定應該怎樣調用該方法。
(譯註:對於一個方法,在當前的添加了相應參數的狀況下,能夠簡單地加裝飾器,區分它是哪一種方法,調用時也容易區分調用;可是,若是沒有加參數,即便能夠用神奇的自動機制來區分出它是哪一種方法,但在調用時,你很差肯定該怎麼調用)。
在評論中,我看到了一些很是極端的對 Bruce 的提議的附和,但一般的代價是使得規則難以遵循,或者要求對語言進行更深層的修改,這令咱們極其難以接受它,特別是合入 Python 3.1。順便說一句,對於 3.1,再次聲明咱們的規則,新特性只有在保持向後兼容的狀況下才是可接受的。
有一個彷佛可行的建議(可使它向後兼容)是把類中的
def foo(self, arg): ...
複製代碼
改爲這樣的語法糖:
def self.foo(arg): ...
複製代碼
但我不認同它把「self」變爲保留字(reserved word),或者要求前綴必須是「self」。若是這樣作了,那對於類方法,很容易也出現這種狀況:
@classmethod
def cls.foo(arg): ...
複製代碼
好了,相比於現狀,我並無更喜歡這個。可是相比於 Bruce 的提議或在他的博客評論區中提出的更極端的說法,我認爲這個要好得多,並且它具備向後兼容的巨大優點,而且不須要很費力,就能夠寫成帶有參考實現的 PEP。(我想 Bruce 應該會發現本身提案中的缺陷,若是他真的付出努力嘗試編寫可靠的 PEP 或者嘗試實現它。)
我能夠繼續聊不少,但這是一個陽光明媚的週日早晨,而我還有其它的計劃... :-)
做者:Guido van Rossum,寫於:2008.10.26
英文: neopythonic.blogspot.com/2008/10/why…
做者簡介: Guido van Rossum,Python 的創造者,一直是「終身仁慈獨裁者」,直到 2018 年 7 月 12 日退位。目前,他是新的最高決策層的五位成員之一,依然活躍在社區中。
譯者簡介: 豌豆花下貓,生於廣東畢業於武大,現爲蘇漂程序員,有一些極客思惟,也有一些人文情懷,有一些溫度,還有一些態度。公衆號:「Python貓」(python_cat)。
公衆號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫做、優質英文推薦與翻譯等等,歡迎關注哦。