App下載

python下劃線的多種應(yīng)用場(chǎng)景總結(jié)

一夜奈良山 2021-08-18 10:01:36 瀏覽數(shù) (2291)
反饋

小編剛開始學(xué)習(xí)python的時(shí)候被__future__的用法吸引到了。這是一種神奇的包,可以在當(dāng)前版本引用下一個(gè)版本的新特性,這樣的方法被稱為魔術(shù)方法。但其實(shí)給小編留下深刻印象的不只是這神奇的用法,而是那兩條下劃線,小編當(dāng)時(shí)就感覺下劃線在python中是有大用途的。今天小編就進(jìn)行了下劃線常見的應(yīng)用場(chǎng)景總結(jié)。讓我們來(lái)看看下劃線有什么神奇的用法吧。

目前常見的用法有五種:

  • _用于臨時(shí)變量
  • var_用于解決命名沖突問(wèn)題
  • _var用于保護(hù)變量
  • __var用于私有變量
  • __var__用于魔術(shù)方法

下面我們具體看看這些下劃線應(yīng)用場(chǎng)景。

一、_用于臨時(shí)變量

單下劃線一般用于表示臨時(shí)變量,在REPL、for循環(huán)和元組拆包等場(chǎng)景中比較常見。

1.1 REPL

單下劃線在REPL中關(guān)聯(lián)的是上一次計(jì)算的非None結(jié)果。

>>> 1+1
2
>>> _
2
>>> a=2+2
>>> _
2

1+1,結(jié)果為2,賦值給_;而賦值表達(dá)式a=2+2a為4,但整個(gè)表達(dá)式結(jié)果為None,故不會(huì)關(guān)聯(lián)到_。這有點(diǎn)類似日常大家使用的計(jì)算器中的ANS按鍵,直接保存了上次的計(jì)算結(jié)果。

1.2 for循環(huán)中的_

for循環(huán)中_作為臨時(shí)變量用。下劃線來(lái)指代沒(méi)什么意義的變量。例如在如下函數(shù)中,當(dāng)我們只關(guān)心函數(shù)執(zhí)行次數(shù),而不關(guān)心具體次序的情況下,可以使用_作為參數(shù)。

nums = 13
for _ in range(nums):
    fun_oper()

1.3 元組拆包中的_

第三個(gè)用法是元組拆包,賦值的時(shí)候可以用_來(lái)表示略過(guò)的內(nèi)容。如下代碼忽略北京市人口數(shù),只取得名字和區(qū)號(hào)。

>>> city,_,code = ('Beijing',21536000,'010')
>>> print(city,code)
Beijing 010

如果需要略過(guò)的內(nèi)容多于一個(gè)的話,可以使用*開頭的參數(shù),表示忽略多個(gè)內(nèi)容。如下代碼忽略面積和人口數(shù),只取得名字和區(qū)號(hào)

city,*_,code = ('Beijing',21536000,16410.54,'010')

1.4 國(guó)際化函數(shù)

在一些國(guó)際化編程中,_常用來(lái)表示翻譯函數(shù)名。例如gettext包使用時(shí):

import gettext
zh = gettext.tranlation('dict','locale',languages=['zh_CN'])
zh.install()
_('hello world')

依據(jù)設(shè)定的字典文件,其返回相應(yīng)的漢字“你好世界”。

1.5 大數(shù)字表示形式

_也可用于數(shù)字的分割,這在數(shù)字比較長(zhǎng)的時(shí)候常用。

>>> a = 9_999_999_999
>>> a
9999999999

a的值自動(dòng)忽略了下劃線。這樣用_分割數(shù)字,有利于便捷讀取比較大的數(shù)。

二、var_用于解決命名沖突問(wèn)題

變量后面加一個(gè)下劃線。主要用于解決命名沖突問(wèn)題,元編程中遇時(shí)Python保留的關(guān)鍵字時(shí),需要臨時(shí)創(chuàng)建一個(gè)變量的副本時(shí),都可以使用這種機(jī)制。

def type_obj_class(name,class_):
    pass

def tag(name,*content,class_):
    pass

以上代碼中出現(xiàn)的class是Python的保留關(guān)鍵字,直接使用會(huì)報(bào)錯(cuò),使用下劃線后綴的方式解決了這個(gè)問(wèn)題。

三、_var用于保護(hù)變量

前面一個(gè)下劃線,后面加上變量,這是僅供內(nèi)部使用的“保護(hù)變量”。比如函數(shù)、方法或者屬性。

這種保護(hù)不是強(qiáng)制規(guī)定,而是一種程序員的約定,解釋器不做訪問(wèn)控制。一般來(lái)講這些屬性都作為實(shí)現(xiàn)細(xì)節(jié)而不需要調(diào)用者關(guān)心,隨時(shí)都可能改變,我們編程時(shí)雖然能訪問(wèn),但是不建議訪問(wèn)。

這種屬性,只有在導(dǎo)入時(shí),才能發(fā)揮保護(hù)作用。而且必須是from XXX import *這種導(dǎo)入形式才能發(fā)揮保護(hù)作用。

使用from XXX import *是一種通配導(dǎo)入(wildcard import),這是Python社區(qū)不推薦的方式,因?yàn)槟愀靖悴磺迥愕降讓?dǎo)入了什么屬性、方法,很可能搞亂你自己的命名空間。PEP8推薦的導(dǎo)入方式是from XXX import aVar , b_func , c_func這種形式。

比如在下例汽車庫(kù)函數(shù)tools.py里定義的“保護(hù)屬性”:發(fā)動(dòng)機(jī)型號(hào)和輪胎型號(hào),這屬于實(shí)現(xiàn)細(xì)節(jié),沒(méi)必要暴露給用戶。當(dāng)我們使用from tools import * 語(yǔ)句調(diào)用時(shí),其實(shí)際并沒(méi)有導(dǎo)入所有_開頭的屬性,只導(dǎo)入了普通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函數(shù)被導(dǎo)入進(jìn)來(lái),其他下劃線開頭的“私有屬性”都沒(méi)有導(dǎo)入進(jìn)來(lái)。

{'__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 突破保護(hù)屬性

之所以說(shuō)是“保護(hù)”并不是“私有”,是因?yàn)镻ython沒(méi)有提供解釋器機(jī)制來(lái)控制訪問(wèn)權(quán)限。我們依然可以訪問(wèn)這些屬性:

import tools
tools._moto_type = 'EA211'
tools.drive()

以上代碼,以越過(guò)“保護(hù)屬性”。此外,還有兩種方法能突破這個(gè)限制,一種是將“私有屬性”添加到tool.py文件的__all__列表里,使from tools import *也導(dǎo)入這些本該隱藏的屬性。

__all__ = ['drive','_moto_type','_wheel_type']

另一種是導(dǎo)入時(shí)指定“受保護(hù)屬性”名。

from tools import drive,_start_engine
_start_engine()

甚至是,使用import tools也可以輕易突破保護(hù)限制。所以可見,“保護(hù)屬性”是一種簡(jiǎn)單的隱藏機(jī)制,只有在from tools import *時(shí),由解釋器提供簡(jiǎn)單的保護(hù),但是可以輕易突破。這種保護(hù)更多地依賴程序員的共識(shí):不訪問(wèn)、修改“保護(hù)屬性”。除此之外,有沒(méi)有更安全的保護(hù)機(jī)制呢?有,就是下一部分討論的私有變量。

四、__var用于私有變量

私有屬性解決的之前的保護(hù)屬性保護(hù)力度不夠的問(wèn)題。變量前面加上兩個(gè)下劃線,類里面作為屬性名和方法都可以。兩個(gè)下劃線屬性由Python的改寫機(jī)制來(lái)實(shí)現(xiàn)對(duì)這個(gè)屬性的保護(hù)。

看下面汽車?yán)又校放茷槠胀▽傩?,發(fā)動(dòng)機(jī)為“保護(hù)屬性”,車輪品牌為“私有屬性”。

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']

可見,實(shí)例化car1中,普通屬性self.brand和保護(hù)屬性self._moto_type都得以保存,兩個(gè)下劃線的私有屬性__wheel_type沒(méi)有了。取而代之的是_Car_wheel_type這個(gè)屬性。這就是改寫機(jī)制(Name mangling)。兩個(gè)下劃線的屬性,被改寫成帶有類名前綴的變量,這樣子類很難明明一個(gè)和如此復(fù)雜名字重名的屬性。保證了屬性不被重載,保證了其的私有性。

4.1 突破私有屬性

這里“私有變量”的實(shí)現(xiàn),是從解釋器層面給與的改寫,保護(hù)了私有變量。但是這個(gè)機(jī)制并非絕對(duì)安全,因?yàn)槲覀円廊豢梢酝ㄟ^(guò)obj._ClasssName__private來(lái)訪問(wèn)__private私有屬性。

car1.brand = 'Toyota'
car1._moto_type = '6AR-FSE'
car1._Car__wheel_type = 'BRIDGESTONE'
car1.drive()

結(jié)果

Start the engine 6AR-FSE,
drive the wheel BRIDGESTONE,
I get a running Toyota car

可見,對(duì)改寫機(jī)制改寫的私有變量,雖然保護(hù)性加強(qiáng)了,但依然可以訪問(wèn)并修改。只是這種修改,只是一種雜耍般的操作,并不可取。

五、__var__用于魔術(shù)方法

變量前面兩個(gè)下劃線,后面兩個(gè)下劃線。這是Python當(dāng)中的魔術(shù)方法,一般是給系統(tǒng)程序調(diào)用的。例如上例中的__init__就是類的初始化魔術(shù)方法,還有支持len函數(shù)的__len__方法,支持上下文管理器協(xié)議的__enter__和__exit__方法,支持迭代器協(xié)議的__iter__方法,支持格式化顯示的__repr__和__str__方法等等。這里我們?yōu)樯侠腃ar類添加魔術(shù)方法__repr__來(lái)支持格式化顯示。

    def __repr__(self):
        return '***Car %s:with %s Engine,%sWheel***'%
        (self.brand,self._moto_type,self.__wheel_type)

未添加__repr__魔術(shù)方法之前,print(car1)結(jié)果為<__main__.Car object at 0x0047F7F0>,這個(gè)結(jié)果讓人看的一頭霧水,增加repr魔術(shù)方法之后,顯示結(jié)果為***Car Toyota:with 6AR-FSE Engine,BRIDGESTONE Wheel***清晰明了,利于調(diào)試。這就是魔術(shù)方法的功效:支持系統(tǒng)調(diào)用,改進(jìn)用戶類表現(xiàn),增加協(xié)議支持,使用戶類表現(xiàn)得更像系統(tǒng)類。

5.1 Python魔術(shù)方法分類

以下所有魔術(shù)方法均需要在前后加上__,這里省略了這些雙下劃線。

  • 一元運(yùn)算符 neg pos abs invert
  • 轉(zhuǎn)換 complex int float round inex
  • 算術(shù)運(yùn)算 add sub mul truediv floordiv mod divmod pow lshift rshift and xor or

算術(shù)運(yùn)算除and之外,前面再加上r,表示反運(yùn)算。除dimod外,前面加上i,表示就地運(yùn)算。

  • 比較 lt le eq ne gt ge
  • 類屬性 getattr getattribute setattr delattr dir get set delete
  • 格式化 bytes hash bool format
  • 類相關(guān) init del new
  • 列表 getitem
  • 迭代器 iter next
  • 上下文管理器 enter exit

六、總結(jié)

總之,下劃線在 Python 當(dāng)中應(yīng)用還是很廣泛的,甚至可以說(shuō) Python 對(duì)下劃線有所偏愛

可以看到 _常用于臨時(shí)變量,在REPL,for循環(huán),元組拆包和國(guó)際化中得到了廣泛應(yīng)用

var_用于解決命名沖突問(wèn)題,使用時(shí)比較簡(jiǎn)單易懂的。_var對(duì)變量的保護(hù),只是一種脆弱的保護(hù),更多依靠程序員的約定。__var用于私有變量,借助改寫機(jī)制支持,已經(jīng)支持了私有變量,但是仍然存在漏洞

對(duì)__var__用于魔術(shù)方法,進(jìn)行了一個(gè)簡(jiǎn)單的介紹,魔術(shù)方法較多,但是理解并不復(fù)雜。希望以后可以進(jìn)一步介紹這些魔術(shù)方法

以上就是下劃線常見的應(yīng)用場(chǎng)景總結(jié),更多python學(xué)習(xí)資料請(qǐng)關(guān)注W3Cschool其它相關(guān)文章!


1 人點(diǎn)贊