詳解 Python 中的下劃線命名規則

在 python 中,下劃線命名規則每每令初學者至關疑惑:單下劃線、雙下劃線、雙下劃線還分先後……那它們的做用與使用場景到底有何區別呢?今天就來聊聊這個話題。前端

一、單下劃線(_)

一般狀況下,單下劃線(_)會在如下3種場景中使用:python

1.1 在解釋器中:

在這種狀況下,「_」表明交互式解釋器會話中上一條執行的語句的結果。這種用法首先被標準CPython解釋器採用,而後其餘類型的解釋器也前後採用。程序員

>>> _ Traceback (most recent call last): 
File "<stdin>", line 1, in <module> 
NameError: name '_' is not defined 
>>> 42
>>> _ 
42
>>> 'alright!' if _ else ':('
'alright!'
>>> _ 
'alright!'

1.2 做爲一個名稱:

這與上面一點稍微有些聯繫,此時「_」做爲臨時性的名稱使用。這樣,當其餘人閱讀你的代碼時將會知道,你分配了一個特定的名稱,可是並不會在後面再次用到該名稱。例如,下面的例子中,你可能對循環計數中的實際值並不感興趣,此時就可使用「_」。django

n = 42
for _ in range(n): 
    do_something()

1.3 國際化:

也許你也曾看到」_「會被做爲一個函數來使用。這種狀況下,它一般用於實現國際化和本地化字符串之間翻譯查找的函數名稱,這彷佛源自並遵循相應的C約定。例如,在Django文檔「轉換」章節中,你將能看到以下代碼:編程

from django.utils.translation import ugettext as _ 
from django.http import HttpResponse 
def my_view(request): 
    output = _("Welcome to my site.") 
    return HttpResponse(output)

能夠發現,場景二和場景三中的使用方法可能會相互衝突,因此咱們須要避免在使用「_」做爲國際化查找轉換功能的代碼塊中同時使用「_」做爲臨時名稱。ide

二、名稱前的單下劃線(如:_shahriar)

程序員使用名稱前的單下劃線,用於指定該名稱屬性爲「私有」。這有點相似於慣例,爲了使其餘人(或你本身)使用這些代碼時將會知道以「_」開頭的名稱只供內部使用。正如Python文檔中所述:
如下劃線「_」爲前綴的名稱(如_spam)應該被視爲API中非公開的部分(無論是函數、方法仍是數據成員)。此時,應該將它們看做是一種實現細節,在修改它們時無需對外部通知。
正如上面所說,這確實相似一種慣例,由於它對解釋器來講確實有必定的意義,若是你寫了代碼「from <模塊/包名> import *」,那麼以「_」開頭的名稱都不會被導入,除非模塊或包中的「__all__」列表顯式地包含了它們。瞭解更多請查看「 Importing * in Python 」。
不過值得注意的是,若是使用 import a_module 這樣導入模塊,仍然能夠用 a_module._some_var 這樣的形式訪問到這樣的對象。函數式編程

另外單下劃線開頭還有一種通常不會用到的狀況在於使用一個 C 編寫的擴展庫有時會用下劃線開頭命名,而後使用一個去掉下劃線的 Python 模塊進行包裝。如 struct 這個模塊其實是 C 模塊 _struct 的一個 Python 包裝。函數

三、名稱前的雙下劃線(如:__shahriar)

名稱(具體爲一個方法名)前雙下劃線(__)的用法並非一種慣例,對解釋器來講它有特定的意義。Python中的這種用法是爲了不與子類定義的名稱衝突。Python文檔指出,「__spam」這種形式(至少兩個前導下劃線,最多一個後續下劃線)的任何標識符將會被「_classname__spam」這種形式原文取代,在這裏「classname」是去掉前導下劃線的當前類名。例以下面的例子:工具

>>> class A(object): 
...     def _internal_use(self): 
...         pass
...     def __method_name(self): 
...         pass
... 
>>> dir(A()) 
['_A__method_name', ..., '_internal_use']

正如所預料的,「_internal_use」並未改變,而「__method_name」卻被變成了「_ClassName__method_name」:__開頭 的 私有變量會在代碼生成以前被轉換爲長格式(變爲公有)。轉換機制是這樣的:在變量前端插入類名,再在前端加入一個下劃線字符。這就是所謂的私有變量 名字改編 (Private name mangling) 。 此時,若是你建立A的一個子類B,那麼你將不能輕易地覆寫A中的方法「__method_name」,測試

>>> class B(A): 
...     def __method_name(self): 
...         pass
... 
>>> dir(B()) 
['_A__method_name', '_B__method_name', ..., '_internal_use']

然而若是你知道了這個規律,最終你仍是能夠訪問這個「私有」變量的。
私有變量名字改編意在給出一個在類中定義"私有"實例變量和方法的簡單途徑,避免派生類的實例變量定義產生問題,或者與外界代碼中的變量搞混。
要注意的是混淆規則(私有變量名字改編)主要目的在於避免意外錯誤,被認做爲私有的變量仍然有可能被訪問或修改(使用_classname__membername),在特定的場合它也是有用的,好比調試的時候。

上述的功能幾乎和Java中的final方法和C++類中標準方法(非虛方法)同樣。

再講兩點題外話:
一是由於軋壓(改編)會使標識符變長,當超過255的時候,Python會切斷,要注意所以引發的命名衝突。
二是當類名所有如下劃線命名的時候,Python就再也不執行軋壓(改編)。

不管是單下劃線仍是雙下劃線開頭的成員,都是但願外部程序開發者不要直接使用這些成員變量和這些成員函數,只是雙下劃線從語法上可以更直接的避免錯誤的使用,可是若是按照 _類名__成員名 則依然能夠訪問到。單下劃線的在動態調試時可能會方便一些,只要項目組的人都遵照下劃線開頭的成員不直接使用,那使用單下劃線或許會更好。

四、名稱先後的雙下劃線(如:__init__)

這種用法表示Python中特殊的方法名。其實,這只是一種慣例,對Python系統來講,這將確保不會與用戶自定義的名稱衝突。一般,你將會覆寫這些方法,並在裏面實現你所須要的功能,以便Python調用它們。例如,當定義一個類時,你常常會覆寫「__init__」方法。

雙下劃線開頭雙下劃線結尾的是一些 Python 的「魔術」對象,如類成員的 __init__、__del__、__add__、__getitem__ 等,以及全局的 __file__、__name__ 等。 Python 官方推薦永遠不要將這樣的命名方式應用於本身的變量或函數,而是按照文檔說明來使用。雖然你也能夠編寫本身的特殊方法名,但不要這樣作。

>>> class C(object): 
...     def __mine__(self): 
...         pass
... 
>>> dir(C) 
... [..., '__mine__', ...]

其實,很容易擺脫這種類型的命名,而只讓Python內部定義的特殊名稱遵循這種約定 :)

五、題外話 if __name__ == "__main__":

全部的 Python 模塊都是對象而且有幾個有用的屬性,你可使用這些屬性方便地測試你所書寫的模塊。

模塊是對象, 而且全部的模塊都有一個內置屬性 __name__。一個模塊的 __name__ 的值要看您如何應用模塊。若是 import 模塊, 那麼 __name__的值一般爲模塊的文件名, 不帶路徑或者文件擴展名。可是您也能夠像一個標準的程序同樣直接運行模塊, 在這種狀況下 __name__的值將是一個特別的缺省值:__main__。

>>> import odbchelper
>>> odbchelper.__name__
'odbchelper'

一旦瞭解到這一點, 您能夠在模塊內部爲您的模塊設計一個測試套件, 在其中加入這個 if 語句。當您直接運行模塊, __name__ 的值是 __main__, 因此測試套件執行。當您導入模塊, __name__的值就是別的東西了, 因此測試套件被忽略。這樣使得在將新的模塊集成到一個大程序以前開發和調試容易多了。
在 MacPython 上, 須要一個額外的步聚來使得 if __name__ 技巧有效。 點擊窗口右上角的黑色三角, 彈出模塊的屬性菜單, 確認 Run as __main__ 被選中。

六、用 __all__ 暴露接口

Python 能夠在模塊級別暴露接口:

__all__ = ["foo", "bar"]

不少時候這麼作仍是頗有好處的……

提供了哪些是公開接口的約定

不像 Ruby 或者 Java,Python 沒有語言原生的可見性控制,而是靠一套須要你們自覺遵照的」約定「下工做。好比下劃線開頭的應該對外部不可見。一樣,__all__ 也是對於模塊公開接口的一種約定,比起下劃線,__all__ 提供了暴露接口用的」白名單「。一些不如下劃線開頭的變量(好比從其餘地方 import 到當前模塊的成員)能夠一樣被排除出去。

import os
import sys

__all__ = ["process_xxx"]  # 排除了 `os` 和 `sys`

def process_xxx():
    pass  # omit

 

6.1 控制 from xxx import * 的行爲

代碼中固然是不提倡用 from xxx import * 的寫法的(由於斷定一個特殊的函數或屬性是從哪來的有些困難,而且會形成調試和重構都更困難。),可是在 console 調試的時候圖個方便仍是很常見的。若是一個模塊 spam 沒有定義 __all__,執行 from spam import * 的時候會將 spam 中非下劃線開頭的成員都導入當前命名空間中,這樣固然就有可能弄髒當前命名空間。若是顯式聲明瞭 __all__,import * 就只會導入 __all__ 列出的成員。若是 __all__ 定義有誤,列出的成員不存在,還會明確地拋出異常,而不是默默忽略。

6.2 爲 lint 工具提供輔助

編寫一個庫的時候,常常會在 __init__.py 中暴露整個包的 API,而這些 API 的實現多是在包中其餘模塊中定義的。若是咱們僅僅這樣寫:

from foo.bar import Spam, Egg

一些代碼檢查工具,如 pyflakes 就會報錯,認爲 Spam 和 Egg 是 import 了又沒被使用的變量。固然一個可行的方法是把這個警告壓掉:

from foo.bar import Spam, Egg  # noqa

可是更好的方法是顯式定義 __all__,這樣代碼檢查工具會理解這層意思,就再也不報 unused variables 的警告:

from foo.bar import Spam, Egg

__all__ = ["Spam", "Egg"]

須要注意的是大部分狀況下 __all__ 都是一個 list,而不是 tuple 或者其餘序列類型。若是寫了其餘類型的 __all__,如無心外 pyflakes 等 lint 工具會沒法識別出。

6.3 定義 all 須要注意的地方

如上所述,__all__ 應該是 list 類型的

不該該動態生成 __all__,好比使用列表解析式。__all__ 的做用就是定義公開接口,若是不以字面量的形式顯式寫出來,就失去意義了。

即便有了 __all__ 也不該該在非臨時代碼中使用 from xxx import * 語法,或者用元編程手段模擬 Ruby 的自動 import。Python 不像 Ruby,沒有 Module 這種成員,模塊就是命名空間隔離的執行者。若是打破了這一層,並且引入諸多動態因素,生產環境跑的代碼就充滿了不肯定性,調試也會很是困難。

按照 PEP8 建議的風格,__all__ 應該寫在全部 import 語句下面,和函數、常量等模塊成員定義的上面。

若是一個模塊須要暴露的接口改動頻繁,__all__ 能夠這樣定義:

__all__ = [

    "foo",

    "bar",

    "egg",

]

最後多出來的逗號在 Python 中是容許的,也是符合 PEP8 風格的。這樣修改一個接口的暴露就只修改一行,方便版本控制的時候看 diff。

七、總結:

Python 用下劃線做爲變量前綴和後綴指定特殊變量。
_xxx       不能用'from module import *'導入
__xxx__  系統定義名字
__xxx     類中的私有變量名
核心風格:避免用下劃線做爲變量名的開頭。
由於下劃線對解釋器有特殊的意義,並且是內建標識符所使用的符號,咱們建議程序員避免用下劃線做爲變量名的開頭。

通常來說,變量名_xxx被看做是「私有的」,在模塊或類外不可使用。當變量是私有的時候,用_xxx 來表示變量是很好的習慣。

由於變量名__xxx__對Python 來講有特殊含義,對於普通的變量應當避免這種命名風格。
"單下劃線" 開始的成員變量叫作保護變量,意思是隻有類對象和子類對象本身能訪問到這些變量;
"雙下劃線" 開始的是私有成員,意思是隻有類對象本身能訪問,連子類對象也不能訪問到這個數據。
以單下劃線開頭(如_foo)的表明不能直接訪問的類屬性,需經過類提供的接口進行訪問,不能用「from xxx import *」而導入(注意 import xxx 是能夠訪問的);以雙下劃線開頭的(如__foo)表明類的私有成員;以雙下劃線開頭和結尾的(__foo__)表明python裏特殊方法專用的標識,如 __init__() 表明類的構造函數。

附 PEP 規範:

PEP-0008:

In addition, the following special forms using leading or trailing underscores are recognized (these can generally be combined with any case convention):

    - _single_leading_underscore: weak "internal use" indicator. E.g. "from M import *" does not import objects whose name starts with an underscore.

    - single_trailing_underscore_: used by convention to avoid conflicts with Python keyword, e.g.

      Tkinter.Toplevel(master, class_='ClassName')

    - __double_leading_underscore: when naming a class attribute, invokes name mangling (inside class FooBar, __boo becomes _FooBar__boo; see below).

    - __double_leading_and_trailing_underscore__: "magic" objects or attributes that live in user-controlled namespaces. E.g. __init__,
      __import__ or __file__. Never invent such names; only use them as documented.

八、Refer:

[1] Importing `*` in Python

http://shahriar.svbtle.com/importing-star-in-python

[2] 理解Python的雙下劃線命名

http://blog.csdn.net/zhu_liangwei/article/details/7667745

[3] Python 的類的下劃線命名有什麼不一樣?

http://www.zhihu.com/question/19754941

[4] 用 __all__ 暴露接口

http://python-china.org/t/725

[5] python基礎(7):變量、參數、函數式編程 —— 關於Python中的變量命名規則

http://my.oschina.net/leejun2005/blog/269921#OSC_h3_3

[6] Python 魔術方法(Magic Method)

https://mp.weixin.qq.com/s/F94rQEgPKfYyPimasZHjhA

相關文章
相關標籤/搜索