1. 程式人生 > >每日一python(11):python中下劃線的意義

每日一python(11):python中下劃線的意義

文章目錄

1 單下劃線(_)

1.1 在直譯器中

單下劃線(_)符號是指互動直譯器中最後一次執行語句的返回結果。這種用法最初出現在CPython直譯器中,其他直譯器後來也都跟進了。

例如:

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

1.2 作為名稱使用

這與上面一點稍微有些聯絡,此時的單下劃線作為臨時性的名稱使用。這樣,當其他人閱讀你的程式碼時將會知道,你分配了一個特定的名稱,但是並不會在後面再次用到該名稱。
例如,下面的例子中,你可能對迴圈計數中的實際值並不感興趣,此時就可以使用(_)。

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

2 變數中的下劃線

2.1 單下劃線開頭的變數(_XXX)

以單下劃線開頭的變數,表明這是一個**受保護(protected)**的變數,原則上不允許直接訪問,但是外部類還是可以訪問到這個變數。因為這只是一個程式設計師之間的約定,用於警告說明這是一個受保護的變數,外部類不要去訪問它。

以單下劃線做字首的變量表名了這個變數是“私有的”。在 有些 匯入( import *) 的場景中,下一個使用你程式碼的人(或者你本人)會明白這個名稱僅內部使用。Python documentation裡面寫道:
在這裡插入圖片描述


以單下劃線’_'為字首的名稱,如_xxx,應該被視為API中非公開的部分(不管是函式,方法還是資料成員)。此時,應該將它們看做一種實現細節,在修改他們時無需對外部通知。

例如:

class Student(object):
    def __init__(self, name, age):
        self._name = name
        self.age = age

st = Student("Yi", 31)
print(st._name)              #   Yi
print(st.age)                #   31

正如上面所說,這確實類似一種慣例,因為它對直譯器來說確實有一定的意義,如果你寫了程式碼 from <模組/包名> import *,那麼以’_'開頭的名稱都不會被匯入,除非模組或包中的__all__列表顯示地包含了它們。不過值得注意的是,如果使用 import module 這樣的方式匯入模組,仍然可以用 module._var這樣的形式訪問到這樣的物件。

例如:

模組test1.py

'''模組test1.py'''
# 定義2個模組變數

num  = 10
_num = 40

模組test2.py

'''模組test2.py'''
from test1 import *

print(num)
print(_num)

列印結果:
在這裡插入圖片描述
從上面的結果可以看到:採用 from <模組/包名> import * 方式匯入時,以’_'開頭的變數不會被匯入!!

但是 當以 import moule 的方式匯入時,以’_'開頭的變數就會被匯入,如下:

模組test3.py

'''模組test3.py'''
import test1

print(test1.num)
print(test1._num)

列印結果:
在這裡插入圖片描述

2.2 雙下劃線開頭的變數(__XXX)

在Python中,例項的變數名如果以雙下劃線( __ )開頭,就變成了一個私有變數(private),只有內部可以訪問,外部不能訪問。因為Python直譯器對外把 __xxx變數改成了_classname__xxx,所以,仍然可以通過_classname__xxx來訪問__xxx變數。

例如:

class Student(object):
    def __init__(self, name, score, age):
        self.__name = name
        self.__score = score
        self.age = age

st = Student("Yi", 88, 31)
print(st.age)      #  返回: 31
print(st.__name)   #  報錯: AttributeError: 'Student' object has no attribute '__name'
print(st._Student__name)      # 返回: Yi

從上面的結果可以看到:這樣就確保了外部程式碼不能隨意修改物件內部的狀態,這樣通過訪問限制的保護,程式碼更加健壯

但是如果外部程式碼一定要獲取namescore怎麼辦?可以給Student類增加get_name()get_score()方法。如下:

class Student(object):
    def __init__(self, name, age):
        self.__name = name
        self.age = age

    def get_name(self):
        return self.__name
    
    def get_score(self):
        return self.__score

st = Student("Yi", 31)
print(st.age)      #  返回: 31
#print(st.__name)   #  報錯: AttributeError: 'Student' object has no attribute '__name'
print(st._Student__name)      # 返回: Yi
print(st.get_name())          # 返回: Yi
print(st.get_score())         # 返回: 88

如果又要允許外部程式碼修改score怎麼辦?可以再給Student類增加set_score()方法,如下:

class Student(object):
    def __init__(self, name, age, score):
        self.__name = name
        self.__score = score
        self.age = age

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

    def set_score(self, score):
        self.__score = score


st = Student("Yi", 31, 88)
print(st.age)      #  返回: 31
#print(st.__name)   #  報錯: AttributeError: 'Student' object has no attribute '__name'
print(st._Student__name)      # 返回: Yi
print(st.get_name())          # 返回: Yi
print("修改前的學生分數:" , st.get_score())    # 返回: 88
st.set_score(95)
print("修改後的學生分數:", st.get_score())     # 返回: 95

也許,我們會有個疑問,直接通過st.score = 99就可以修改啊,這裡為什麼要定義一個方法大費周折呢?
這是因為在方法中,可以對引數做檢查,避免傳入無效的引數。例如:

class Student(object):
    def __init__(self, name, age, score):
        self.__name = name
        self.__score = score
        self.age = age

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')


st = Student("Yi", 31, 88)
print(st.age)           #  返回: 31
#print(st.__name)       #  報錯: AttributeError: 'Student' object has no attribute '__name'
print(st._Student__name)      # 返回: Yi
print(st.get_name())    # 返回: Yi
print("修改前的學生分數:" , st.get_score())    # 返回: 88
st.set_score(95)
print("修改後的學生分數:", st.get_score())     # 返回: 95
st.set_score(120)       # 報錯:raise ValueError('bad score')

2.3 雙下劃線開頭和結尾的變數( __ XXX__)

在Python中,類似__xxx__的變數名,也就是以雙下劃線開頭,並且以雙下劃線結尾的變數,是 特殊變數, 也可以稱之為內建變數,。
特殊變數是可以直接訪問的,不是private變數,、如__init____import__或是__file__。所以,最好不要自己定義這類變數。

3 方法中開頭和結尾的雙下劃線

這些是Python的特殊方法名,這僅僅是一種慣例,一種確保Python系統中的名稱不會跟使用者自定義的名稱發生衝突的方式。
通常你可以覆寫這些方法,在Python呼叫它們時,產生你想得到的行為。例如,當寫一個類的時候經常會覆寫__init__方法。

4 結論

1、_xxx 不能用於from module import * 以單下劃線開頭的表示的是 受保護的(protected) 型別的變數。保護型別變數只能允許其本身與其子類進行訪問。

2、__xxx 雙下劃線開頭的變量表示的是私有型別(private)的變數。只能是允許這個類本身進行訪問了,連子類也不可以

3、__xxx___ 定義的是特列方法。像__init__之類的