前言 Python 的代碼風格由 PEP 8 描述。這個文檔描述了 Python 編程風格的方方面面。在遵守這個文檔的條件下,不同程式員編寫的 Python 代碼可以保持最大程度的相似風格。這樣就易於閱讀,易於在程式員之間交流。 我們大家在學習Python的時候,好像很多人都不理解為什麼在方法(me ...
前言
Python 的代碼風格由 PEP 8 描述。這個文檔描述了 Python 編程風格的方方面面。在遵守這個文檔的條件下,不同程式員編寫的 Python 代碼可以保持最大程度的相似風格。這樣就易於閱讀,易於在程式員之間交流。
我們大家在學習Python的時候,好像很多人都不理解為什麼在方法(method)前面會加好幾個下劃線,有時甚至兩邊都會加,比如像__this__這種。在我看到上面的文章之前,我一直以為Python中這些下劃線的作用就像Golang中方法/函數的大小寫一樣,或是一些其他語言中的private、public的作用一樣,但仔細深究,這不全是Python這樣設計的初衷。
下麵我們具體分析,話不多說了,來一起看看吧。
單下劃線開頭
我們經常看到方法或者屬性前面加了單下劃線,並認為它表示該方法或者屬性是該類型(Python和Golang一樣,不光類可以有方法,很多類型甚至基本類型也可以定義方法)的私有方法或屬性。但其實在Python中不存在真正意義上的私有方法或者屬性,前面加單下劃線_只是表示你不應該去訪問這個方法或者屬性,因為它不是API的一部分。
舉個例子:
Python class BaseForm(StrAndUnicode): ... def _get_errors(self): "Returns an ErrorDict for the data provided for the form" if self._errors is None: self.full_clean() return self._errors errors = property(_get_errors)
該代碼片段來自Django源碼(django/forms/forms.py)。這段代碼的設計就是errors屬性是對外API的一部分,如果你想獲取錯誤詳情,應該訪問errors屬性,而不是(也不應該)訪問_get_errors方法。
雙下劃線開頭
之前很多人跟我說Python中雙下劃線開頭表示私有,我在很多地方也見到這樣的說法。這樣理解可能也不能說錯,但這不是Python設計雙下劃線開頭的初衷和目的,Python設計此的真正目的僅僅是為了避免子類覆蓋父類的方法。
我們看個例子:
class A(object): def __method(self): print("I'm a method in class A") def method_x(self): print("I'm another method in class A\n") def method(self): self.__method() self.method_x() class B(A): def __method(self): print("I'm a method in class B") def method_x(self): print("I'm another method in class B\n") if __name__ == '__main__': print("situation 1:") a = A() a.method() b = B() b.method() print("situation 2:") # a.__method() a._A__method()
執行結果:
situation 1: I'm a method in class A I'm another method in class A I'm a method in class A I'm another method in class B situation 2: I'm a method in class A
這裡有兩個點需要註意:
A類中我們定義了__method()、method_x和method()三個方法;然後我們重新定義一個類B,繼承自A,並且在B類中覆寫(override)了其父類的__method()和method_x方法,但是從輸出結果看,B對象調用method()方法時調用了其父類A的__method()方法和自己的method_x()方法。也就是說,__method()覆寫沒有生效,而method_x()覆寫生效了。而這也正是Python設計雙下劃線開頭的唯一目的。
這一點也可在Python官方說明中得到答案:https://www.python.org/dev/peps/pep-0008/#method-names-and-instance-variables。
前面我們就說了,Python中不存在真正意義上的私有變數。對於雙下劃線開頭的方法和屬性雖然我們不能直接引用,那是因為Python預設在其前面加了首碼_類名,所以就像situation 2下麵的代碼,雖然我們不能用a直接訪問__method(),但卻可以加上首碼去訪問,即_A__method()。
開頭結尾雙下劃線
一般來說像__this__這種開頭結尾都加雙下劃線的方法表示這是Python自己調用的,你不要調用。比如我們可以調用len()函數來求長度,其實它後臺是調用了__len__()方法。一般我們應該使用len,而不是直接使用__len__():
a = [1, 2, 3] print(len(a)) print(a.__len__()) # 和上面等效 num = 10 print(num + 10) print(num.__add__(10)) # 和上面等效
我們一般稱__len__()這種方法為magic methods,一些操作符後臺調用的也是也是這些magic methods,比如+後臺調用的是__add__,-調用的是__sub__,所以這種機制使得我們可以在自己的類中覆寫操作符(見後面例子)。另外,有的時候這種開頭結尾雙下劃線方法僅僅是某些特殊場景的回調函數,比如__init__()會在對象的初始化時調用,__new__()會在構建一個實例的時候調用等等。下麵我們看兩個例子:
class CrazyNumber(object): def __init__(self, n): self.n = n def __add__(self, other): return self.n - other def __sub__(self, other): return self.n + other def __str__(self): return str(self.n) num = CrazyNumber(10) print(num) # output is: 10 print(num + 5) # output is: 5 print(num - 20) # output is: 30
在上面這個例子中,我們覆寫了+和-操作符,將他們的功能交換了。再看個例子:
class Room(object): def __init__(self): self.people = [] def add(self, person): self.people.append(person) def __len__(self): return len(self.people) room = Room() room.add("Igor") print len(room) # output is: 1
這個例子中,因為我們實現了__len__(),所以Room對象也可以使用len函數了。
所有此類的方法都在這裡有說明:documentation.
結論
- 使用單下劃線(_one_underline)開頭表示方法不是API的一部分,不要直接訪問(雖然語法上訪問也沒有什麼問題)。
- 使用雙下劃線開頭(__two_underlines)開頭表示子類不能覆寫該方法。除非你真的知道你在乾什麼,否則不要使用這種方式。
- 當你想讓自己定義的對象也可以像Python內置的對象一樣使用Python內置的一些函數或操作符(比如len、add、+、-、==等)時,你可以定義該類方法。
- 當然還有些屬性只在末尾加了但下劃線,這僅僅是為了避免我們起的一些名字和Python保留關鍵字衝突,沒有特殊含義。
註:本文大部分內容參考自Difference between _ , and __xx in Python .
http://igorsobreira.com/2010/09/16/difference-between-one-underline-and-two-underlines-in-python.html