python 下劃線的多種應用場景總結
目前常見的用法有五種:
- _用於臨時變量
- var_用於解決命名沖突問題
- _var用於保護變量
- __var用於私有變量
- __var__用於魔術方法
下面我們具體看看這些下劃線應用場景。
一、_用於臨時變量
單下劃線一般用於表示臨時變量,在REPL、for循環和元組拆包等場景中比較常見。
1.1 REPL
單下劃線在REPL中關聯的是上一次計算的非None結果。
>>> 1+1 2 >>> _ 2 >>> a=2+2 >>> _ 2
1+1,結果為2,賦值給_;而賦值表達式a=2+2a為4,但整個表達式結果為None,故不會關聯到_。這有點類似日常大傢使用的計算器中的ANS按鍵,直接保存瞭上次的計算結果。
1.2 for循環中的_
for循環中_作為臨時變量用。下劃線來指代沒什麼意義的變量。例如在如下函數中,當我們隻關心函數執行次數,而不關心具體次序的情況下,可以使用_作為參數。
nums = 13 for _ in range(nums): fun_oper()
1.3 元組拆包中的_
第三個用法是元組拆包,賦值的時候可以用_來表示略過的內容。如下代碼忽略北京市人口數,隻取得名字和區號。
>>> city,_,code = ('Beijing',21536000,'010') >>> print(city,code) Beijing 010
如果需要略過的內容多於一個的話,可以使用*開頭的參數,表示忽略多個內容。如下代碼忽略面積和人口數,隻取得名字和區號
city,*_,code = ('Beijing',21536000,16410.54,'010')
1.4 國際化函數
在一些國際化編程中,_常用來表示翻譯函數名。例如gettext包使用時:
import gettext zh = gettext.tranlation('dict','locale',languages=['zh_CN']) zh.install() _('hello world')
依據設定的字典文件,其返回相應的漢字“你好世界”。
1.5 大數字表示形式
_也可用於數字的分割,這在數字比較長的時候常用。
>>> a = 9_999_999_999 >>> a 9999999999
a的值自動忽略瞭下劃線。這樣用_分割數字,有利於便捷讀取比較大的數。
二、var_用於解決命名沖突問題
變量後面加一個下劃線。主要用於解決命名沖突問題,元編程中遇時Python保留的關鍵字時,需要臨時創建一個變量的副本時,都可以使用這種機制。
def type_obj_class(name,class_): pass def tag(name,*content,class_): pass
以上代碼中出現的class是Python的保留關鍵字,直接使用會報錯,使用下劃線後綴的方式解決瞭這個問題。
三、_var用於保護變量
前面一個下劃線,後面加上變量,這是僅供內部使用的“保護變量”。比如函數、方法或者屬性。
這種保護不是強制規定,而是一種程序員的約定,解釋器不做訪問控制。一般來講這些屬性都作為實現細節而不需要調用者關心,隨時都可能改變,我們編程時雖然能訪問,但是不建議訪問。
這種屬性,隻有在導入時,才能發揮保護作用。而且必須是from XXX import *
這種導入形式才能發揮保護作用。
使用
from XXX import *
是一種通配導入(wildcard import),這是Python社區不推薦的方式,因為你根本搞不清你到底導入瞭什麼屬性、方法,很可能搞亂你自己的命名空間。PEP8推薦的導入方式是from XXX import aVar , b_func , c_func
這種形式。
比如在下例汽車庫函數tools.py裡定義的“保護屬性”:發動機型號和輪胎型號,這屬於實現細節,沒必要暴露給用戶。當我們使用from tools import *
語句調用時,其實際並沒有導入所有_開頭的屬性,隻導入瞭普通drive方法。
_moto_type = 'L15b2' _wheel_type = 'michelin' def drive(): _start_engine() _drive_wheel() def _start_engine(): print('start engine %s'%_moto_type) def _drive_wheel(): print('drive wheel %s'%_wheel_type)
查看命令空間print(vars())可見,隻有drive函數被導入進來,其他下劃線開頭的“私有屬性”都沒有導入進來。
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x005CF868>, '__spec__': None, '__annotations__':{}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '.\\xiahuaxian.py', '__cached__': None, 'walk': <function walk at 0x01DA8C40>, 'root': '.\\__pycache__', '_': [21536000, 16410.54], 'dirs': ['tools.cpython-38.pyc'], 'city': 'Beijing', 'code': '010', 'drive': <function drive at 0x01DBC4A8>}
3.1 突破保護屬性
之所以說是“保護”並不是“私有”,是因為Python沒有提供解釋器機制來控制訪問權限。我們依然可以訪問這些屬性:
import tools tools._moto_type = 'EA211' tools.drive()
以上代碼,以越過“保護屬性”。此外,還有兩種方法能突破這個限制,一種是將“私有屬性”添加到tool.py文件的__all__列表裡,使from tools import *
也導入這些本該隱藏的屬性。
__all__ = ['drive','_moto_type','_wheel_type']
另一種是導入時指定“受保護屬性”名。
from tools import drive,_start_engine _start_engine()
甚至是,使用import tools
也可以輕易突破保護限制。所以可見,“保護屬性”是一種簡單的隱藏機制,隻有在from tools import *
時,由解釋器提供簡單的保護,但是可以輕易突破。這種保護更多地依賴程序員的共識:不訪問、修改“保護屬性”。除此之外,有沒有更安全的保護機制呢?有,就是下一部分討論的私有變量。
四、__var用於私有變量
私有屬性解決的之前的保護屬性保護力度不夠的問題。變量前面加上兩個下劃線,類裡面作為屬性名和方法都可以。兩個下劃線屬性由Python的改寫機制來實現對這個屬性的保護。
看下面汽車例子中,品牌為普通屬性,發動機為“保護屬性”,車輪品牌為“私有屬性”。
class Car: def __init__(self): self.brand = 'Honda' self._moto_type = 'L15B2' self.__wheel_type = 'michelin' def drive(self): print('Start the engine %s,drive the wheel %s,I get a running %s car'% (self._moto_type, self.__wheel_type, self.brand))
我們用var(car1)查看下具體屬性值,
['_Car__wheel_type', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_moto_type', 'brand', 'drive']
可見,實例化car1中,普通屬性self.brand和保護屬性self._moto_type都得以保存,兩個下劃線的私有屬性__wheel_type沒有瞭。取而代之的是_Car_wheel_type這個屬性。這就是改寫機制(Name mangling)。兩個下劃線的屬性,被改寫成帶有類名前綴的變量,這樣子類很難明明一個和如此復雜名字重名的屬性。保證瞭屬性不被重載,保證瞭其的私有性。
4.1 突破私有屬性
這裡“私有變量”的實現,是從解釋器層面給與的改寫,保護瞭私有變量。但是這個機制並非絕對安全,因為我們依然可以通過obj._ClasssName__private來訪問__private私有屬性。
car1.brand = 'Toyota' car1._moto_type = '6AR-FSE' car1._Car__wheel_type = 'BRIDGESTONE' car1.drive()
結果
Start the engine 6AR-FSE,\
drive the wheel BRIDGESTONE,\
I get a running Toyota car
可見,對改寫機制改寫的私有變量,雖然保護性加強瞭,但依然可以訪問並修改。隻是這種修改,隻是一種雜耍般的操作,並不可取。
五、__var__用於魔術方法
變量前面兩個下劃線,後面兩個下劃線。這是Python當中的魔術方法,一般是給系統程序調用的。例如上例中的__init__就是類的初始化魔術方法,還有支持len函數的__len__方法,支持上下文管理器協議的__enter__和__exit__方法,支持迭代器協議的__iter__方法,支持格式化顯示的__repr__和__str__方法等等。這裡我們為上例的Car類添加魔術方法__repr__來支持格式化顯示。
def __repr__(self): return '***Car %s:with %s Engine,%sWheel***'% (self.brand,self._moto_type,self.__wheel_type)
未添加__repr__魔術方法之前,print(car1)結果為<__main__.Car object at 0x0047F7F0>,這個結果讓人看的一頭霧水,增加repr魔術方法之後,顯示結果為***Car Toyota:with 6AR-FSE Engine,BRIDGESTONE Wheel***清晰明瞭,利於調試。這就是魔術方法的功效:支持系統調用,改進用戶類表現,增加協議支持,使用戶類表現得更像系統類。
5.1 Python魔術方法分類
以下所有魔術方法均需要在前後加上__,這裡省略瞭這些雙下劃線。
- 一元運算符 neg pos abs invert
- 轉換 complex int float round inex
- 算術運算 add sub mul truediv floordiv mod divmod pow lshift rshift and xor or
算術運算除and之外,前面再加上r,表示反運算。除dimod外,前面加上i,表示就地運算。
- 比較 lt le eq ne gt ge
- 類屬性 getattr getattribute setattr delattr dir get set delete
- 格式化 bytes hash bool format
- 類相關 init del new
- 列表 getitem
- 迭代器 iter next
- 上下文管理器 enter exit
六、總結
總之,下劃線在 Python 當中應用還是很廣泛的,甚至可以說 Python 對下劃線有所偏愛
可以看到 _常用於臨時變量,在REPL,for循環,元組拆包和國際化中得到瞭廣泛應用
var_用於解決命名沖突問題,使用時比較簡單易懂的。_var對變量的保護,隻是一種脆弱的保護,更多依靠程序員的約定。__var用於私有變量,借助改寫機制支持,已經支持瞭私有變量,但是仍然存在漏洞
對__var__用於魔術方法,進行瞭一個簡單的介紹,魔術方法較多,但是理解並不復雜。希望以後可以進一步介紹這些魔術方法
以上就是python 下劃線的多種應用場景總結的詳細內容,更多關於python 下劃線應用場景的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 解析python高級異常和運算符重載
- python 特殊屬性及方法詳細解析
- 一文教你將Visual Studio Code變成Python開發神器
- Python的基本語法詳解
- python中為main方法傳參問題