Python的下划線_


1、單下划線(_)

通常情況下,單下划線(_)會在以下3種場景中使用:

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 作為一個名稱:

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

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)

可以發現,場景二和場景三中的使用方法可能會相互沖突,所以我們需要避免在使用“_”作為國際化查找轉換功能的代碼塊中同時使用“_”作為臨時名稱。

2、名稱前的單下划線(如:_shahriar)

程序員使用名稱前的單下划線,用於指定該名稱屬性為“私有”。這有點類似於慣例,為了使其他人(或你自己)使用這些代碼時將會知道以“_”開頭的名稱只供內部使用。正如Python文檔中所述:

以下划線“_”為前綴的名稱(如_spam)應該被視為API中非公開的部分(不管是函數、方法還是數據成員)。此時,應該將它們看作是一種實現細節,在修改它們時無需對外部通知。

正如上面所說,這確實類似一種慣例,因為它對解釋器來說確實有一定的意義,如果你寫了代碼“from <模塊/包名> import *”,那么以“_”開頭的名稱都不會被導入, 除非模塊或包中的“__all__”列表顯式地包含了它們 。了解更多請查看“ Importing * in Python”。

不過值得注意的是,如果使用 import a_module 這樣導入模塊,仍然可以用 a_module._some_var 這樣的形式訪問到這樣的對象。

另外單下划線開頭還有一種一般不會用到的情況在於使用一個 C 編寫的擴展庫有時會用下划線開頭命名,然后使用一個去掉下划線的 Python 模塊進行包裝。如 struct 這個模塊實際上是 C 模塊 _struct 的一個 Python 包裝。

3、名稱前的雙下划線(如:__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就不再執行軋壓(改編)。

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

4、名稱前后的雙下划線(如:__init__)

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

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

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

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

5、題外話 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__ 被選中。

6、總結:

Python 用下划線作為變量前綴和后綴指定特殊變量。

_xxx       不能用'from module import *'導入

__xxx__  系統定義名字

__xxx     類中的私有變量名

核心風格:避免用下划線作為變量名的開頭。

因為下划線對解釋器有特殊的意義,而且是內建標識符所使用的符號,我們建議程序員避免用下划線作為變量名的開頭。一般來講,變量名_xxx被看作是“私有的”,在模塊或類外不可以使用。當變量是私有的時候,用_xxx 來表示變量是很好的習慣。 因為變量名__xxx__對Python 來說有特殊含義,對於普通的變量應當避免這種命名風格。

"單下划線" 開始的成員變量叫做保護變量,意思是只有類對象和子類對象自己能訪問到這些變量;

"雙下划線" 開始的是私有成員,意思是只有類對象自己能訪問,連子類對象也不能訪問到這個數據。

以單下划線開頭(如_foo)的代表不能直接訪問的類屬性,需通過類提供的接口進行訪問,不能用“from xxx import *”而導入;以雙下划線開頭的(如__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.

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
##############################################################################################################################################################################

Python 用下划線作為變量前綴和后綴指定特殊變量

_xxx 不能用’from module import *’導入

__xxx__ 系統定義名字

__xxx 類中的私有變量名

核心風格:避免用下划線作為變量名的開始。

 

因為下划線對解釋器有特殊的意義,而且是內建標識符所使用的符號,我們建議程序員避免用下划線作為變量名的開始。一般來講,變量名_xxx被看作是“私有 的”,在模塊或類外不可以使用。當變量是私有的時候,用_xxx 來表示變量是很好的習慣。因為變量名__xxx__對Python 來說有特殊含義,對於普通的變量應當避免這種命名風格。

“單下划線” 開始的成員變量叫做保護變量,意思是只有類對象和子類對象自己能訪問到這些變量;
“雙下划線” 開始的是私有成員,意思是只有類對象自己能訪問,連子類對象也不能訪問到這個數據。

以單下划線開頭(_foo)的代表不能直接訪問的類屬性,需通過類提供的接口進行訪問,不能用“from xxx import *”而導入;以雙下划線開頭的(__foo)代表類的私有成員;以雙下划線開頭和結尾的(__foo__)代表python里特殊方法專用的標識,如 __init__()代表類的構造函數。

現在我們來總結下所有的系統定義屬性和方法, 先來看下保留屬性:

>>> Class1.__doc__ # 類型幫助信息 'Class1 Doc.' >>> Class1.__name__ # 類型名稱 'Class1' >>> Class1.__module__ # 類型所在模塊 '__main__' >>> Class1.__bases__ # 類型所繼承的基類 (<type 'object'>,) >>> Class1.__dict__ # 類型字典,存儲所有類型成員信息。 <dictproxy object at 0x00D3AD70> >>> Class1().__class__ # 類型 <class '__main__.Class1'> >>> Class1().__module__ # 實例類型所在模塊 '__main__' >>> Class1().__dict__ # 對象字典,存儲所有實例成員信息。 {'i': 1234}
接下來是保留方法,可以把保留方法分類:

類的基礎方法

序號 目的 所編寫代碼 Python 實際調用
初始化一個實例 x = MyClass() x.__init__()
字符串的“官方”表現形式 repr(x) x.__repr__()
字符串的“非正式”值 str(x) x.__str__()
字節數組的“非正式”值 bytes(x) x.__bytes__()
格式化字符串的值 format(x,format_spec) x.__format__(format_spec)
  1. 對 __init__() 方法的調用發生在實例被創建 之后 。如果要控制實際創建進程,請使用 __new__()方法
  2. 按照約定, __repr__() 方法所返回的字符串為合法的 Python 表達式。
  3. 在調用 print(x) 的同時也調用了 __str__() 方法。
  4. 由於 bytes 類型的引入而從 Python 3 開始出現

行為方式與迭代器類似的類

序號 目的 所編寫代碼 Python 實際調用
遍歷某個序列 iter(seq) seq.__iter__()
從迭代器中獲取下一個值 next(seq) seq.__next__()
按逆序創建一個迭代器 reversed(seq) seq.__reversed__()
  1. 無論何時創建迭代器都將調用 __iter__() 方法。這是用初始值對迭代器進行初始化的絕佳之處。
  2. 無論何時從迭代器中獲取下一個值都將調用 __next__() 方法。
  3. __reversed__() 方法並不常用。它以一個現有序列為參數,並將該序列中所有元素從尾到頭以逆序排列生成一個新的迭代器。

計算屬性

序號 目的 所編寫代碼 Python 實際調用
獲取一個計算屬性(無條件的) x.my_property x.__getattribute__('my_property')
獲取一個計算屬性(后備) x.my_property x.__getattr__('my_property')
設置某屬性 x.my_property = value x.__setattr__('my_property',value)
刪除某屬性 del x.my_property x.__delattr__('my_property')
列出所有屬性和方法 dir(x) x.__dir__()
  1. 如果某個類定義了 __getattribute__() 方法,在 每次引用屬性或方法名稱時 Python 都調用它(特殊方法名稱除外,因為那樣將會導致討厭的無限循環)。
  2. 如果某個類定義了 __getattr__() 方法,Python 將只在正常的位置查詢屬性時才會調用它。如果實例 x 定義了屬性color, x.color 將 不會 調用x.__getattr__('color');而只會返回x.color 已定義好的值。
  3. 無論何時給屬性賦值,都會調用 __setattr__() 方法。
  4. 無論何時刪除一個屬性,都將調用 __delattr__() 方法。
  5. 如果定義了 __getattr__() 或 __getattribute__() 方法, __dir__() 方法將非常有用。通常,調用 dir(x) 將只顯示正常的屬性和方法。如果 __getattr()__方法動態處理color 屬性,dir(x) 將不會將 color 列為可用屬性。可通過覆蓋 __dir__() 方法允許將 color 列為可用屬性,對於想使用你的類但卻不想深入其內部的人來說,該方法非常有益。

 

序號 目的 所編寫代碼 Python 實際調用
  序列的長度 len(seq) seq.__len__()
  了解某序列是否包含特定的值 x in seq seq.__contains__(x)

 

序號 目的 所編寫代碼 Python 實際調用
  通過鍵來獲取值 x[key] x.__getitem__(key)
  通過鍵來設置值 x[key] = value x.__setitem__(key,value)
  刪除一個鍵值對 del x[key] x.__delitem__(key)
  為缺失鍵提供默認值 x[nonexistent_key] x.__missing__(nonexistent_key)

 

可比較的類

我將此內容從前一節中拿出來使其單獨成節,是因為“比較”操作並不局限於數字。許多數據類型都可以進行比較——字符串、列表,甚至字典。如果要創建自己的類,且對象之間的比較有意義,可以使用下面的特殊方法來實現比較。

 

序號 目的 所編寫代碼 Python 實際調用
  相等 x == y x.__eq__(y)
  不相等 x != y x.__ne__(y)
  小於 x < y x.__lt__(y)
  小於或等於 x <= y x.__le__(y)
  大於 x > y x.__gt__(y)
  大於或等於 x >= y x.__ge__(y)
  布爾上上下文環境中的真值 if x: x.__bool__()

 

可序列化的類

 

Python 支持 任意對象的序列化和反序列化。(多數 Python 參考資料稱該過程為 “pickling” 和 “unpickling”)。該技術對與將狀態保存為文件並在稍后恢復它非常有意義。所有的 內置數據類型 均已支持 pickling 。如果創建了自定義類,且希望它能夠 pickle,閱讀 pickle 協議 了解下列特殊方法何時以及如何被調用。

 

序號 目的 所編寫代碼 Python 實際調用
  自定義對象的復制 copy.copy(x) x.__copy__()
  自定義對象的深度復制 copy.deepcopy(x) x.__deepcopy__()
  在 pickling 之前獲取對象的狀態 pickle.dump(x, file) x.__getstate__()
  序列化某對象 pickle.dump(x, file) x.__reduce__()
  序列化某對象(新 pickling 協議) pickle.dump(x, file,protocol_version) x.__reduce_ex__(protocol_version)
* 控制 unpickling 過程中對象的創建方式 x = pickle.load(file) x.__getnewargs__()
* 在 unpickling 之后還原對象的狀態 x = pickle.load(file) x.__setstate__()

 

* 要重建序列化對象,Python 需要創建一個和被序列化的對象看起來一樣的新對象,然后設置新對象的所有屬性。__getnewargs__() 方法控制新對象的創建過程,而 __setstate__() 方法控制屬性值的還原方式。

 

可在 with 語塊中使用的類

 

with 語塊定義了 運行時刻上下文環境;在執行 with 語句時將“進入”該上下文環境,而執行該語塊中的最后一條語句將“退出”該上下文環境。

 

序號 目的 所編寫代碼 Python 實際調用
  在進入 with 語塊時進行一些特別操作 with x: x.__enter__()
  在退出 with 語塊時進行一些特別操作 with x: x.__exit__()

 

以下是 with file 習慣用法 的運作方式:

# excerpt from io.py: def _checkClosed(self, msg=None): '''Internal: raise an ValueError if file is closed ''' if self.closed: raise ValueError('I/O operation on closed file.' if msg is None else msg) def __enter__(self): '''Context management protocol. Returns self.'''  self._checkClosed() ①  return self ② def __exit__(self, *args): '''Context management protocol. Calls close()'''  self.close() ③
  1. 該文件對象同時定義了一個 __enter__() 和一個 __exit__() 方法。該 __enter__() 方法檢查文件是否處於打開狀態;如果沒有, _checkClosed() 方法引發一個例外。
  2. __enter__() 方法將始終返回 self —— 這是 with 語塊將用於調用屬性和方法的對象
  3. 在 with 語塊結束后,文件對象將自動關閉。怎么做到的?在 __exit__() 方法中調用了self.close() .

 該 __exit__() 方法將總是被調用,哪怕是在 with 語塊中引發了例外。實際上,如果引發了例外,該例外信息將會被傳遞給 __exit__() 方法。查閱 With 狀態上下文環境管理器 了解更多細節。

真正神奇的東西

如果知道自己在干什么,你幾乎可以完全控制類是如何比較的、屬性如何定義,以及類的子類是何種類型。

 

序號 目的 所編寫代碼 Python 實際調用
  類構造器 x = MyClass() x.__new__()
* 類析構器 del x x.__del__()
  只定義特定集合的某些屬性   x.__slots__()
  自定義散列值 hash(x) x.__hash__()
  獲取某個屬性的值 x.color type(x).__dict__['color'].__get__(x, type(x))
  設置某個屬性的值 x.color = 'PapayaWhip' type(x).__dict__['color'].__set__(x, 'PapayaWhip')
  刪除某個屬性 del x.color type(x).__dict__['color'].__del__(x)
  控制某個對象是否是該對象的實例 your class isinstance(x, MyClass) MyClass.__instancecheck__(x)
  控制某個類是否是該類的子類 issubclass(C, MyClass) MyClass.__subclasscheck__(C)
  控制某個類是否是該抽象基類的子類 issubclass(C, MyABC) MyABC.__subclasshook__(C)

 

python中以雙下划線的是一些系統定義得名稱,讓python以更優雅得語法實行一些操作,本質上還是一些函數和變量,與其他函數和變量無二。
比如x.__add__(y) 等價於 x+y
有一些很常見,有一些可能比較偏,在這里羅列一下,做個筆記,備忘。
x.__contains__(y) 等價於 y in x, 在list,str, dict,set等容器中有這個函數
__base__, __bases__, __mro__, 關於類繼承和函數查找路徑的。
class.__subclasses__(), 返回子類列表
x.__call__(...) == x(...)
x.__cmp__(y) == cmp(x,y)
x.__getattribute__('name') == x.name == getattr(x, 'name'),  比__getattr__更早調用
x.__hash__() == hash(x)
x.__sizeof__(), x在內存中的字節數, x為class得話, 就應該是x.__basicsize__
x.__delattr__('name') == del x.name
__dictoffset__ attribute tells you the offset to where you find the pointer to the __dict__ object in any instance object that has one. It is in bytes.
__flags__, 返回一串數字,用來判斷該類型能否被序列化(if it's a heap type), __flags__ & 512
S.__format__, 有些類有用
x.__getitem__(y) == x[y], 相應還有__setitem__, 某些不可修改類型如set,str沒有__setitem__
x.__getslice__(i, j) == x[i:j], 有個疑問,x='123456789', x[::2],是咋實現得
__subclasscheck__(), check if a class is subclass
__instancecheck__(), check if an object is an instance
__itemsize__, These fields allow calculating the size in bytes of instances of the type. 0是可變長度, 非0則是固定長度
x.__mod__(y) == x%y, x.__rmod__(y) == y%x
x.__module__ , x所屬模塊
x.__mul__(y) == x*y,  x.__rmul__(y) == y*x

__reduce__, __reduce_ex__ , for pickle

__slots__ 使用之后類變成靜態一樣,沒有了__dict__, 實例也不可新添加屬性

__getattr__ 在一般的查找屬性查找不到之后會調用此函數

__setattr__ 取代一般的賦值操作,如果有此函數會調用此函數, 如想調用正常賦值途徑用 object.__setattr__(self, name, value)

__delattr__ 同__setattr__, 在del obj.name有意義時會調用

轉自:https://www.cnblogs.com/skying555/p/6169110.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM