Python學習系列之下劃線與變量命名規則

一、定義

1.1 從入口函數談起

從其他語言比如C++和Java,轉到Python的同學經常遇到這個問題,入口函數是什麼?Python如何執行?

後來我們發現了:

if __name__ == 'main':

但是爲什麼這樣約定?__name__又是什麼意思?這就涉及了Python中變量和函數的命名規則了。涉及單下劃線雙下劃線("dunder")名稱修飾(name mangling)等。

1.2 變量命名

變量名(標識符)是Python的一種原子元素。當變量名被綁定到一個對象的時候,變量名就指代這個對象。當變量名出現在代碼塊中,那它就是本地變量;當變量名出現在模塊中,它就是全局變量。

1.3 下劃線

Python 用下劃線作爲變量前綴和後綴指定特殊變量/方法。單下劃線和雙下劃線在Python變量和方法名稱中都各有其含義。有一些含義僅僅是依照約定,被視作是對程序員的提示;而有一些含義是由Python解釋器嚴格執行的。

用於特殊變量的表示,訪問控制等作用,作爲用戶則需要謹慎。核心原則是避免用下劃線作爲類外變量名的開始。類內則需要按照需要去選擇,但依舊不能用__object__ 形式

因爲下劃線對解釋器有特殊的意義,而且是內建標識符所使用的符號,我們建議程序員避免用下劃線作爲變量名的開始。一般來講,變量名_object被看作是“私有 的”,在模塊或類外不可以使用,不能用'from moduleimport *'導入(但可以用常規導入方法導入)。當變量是私有的時候,用_object來表示變量是很好的習慣。因爲變量名__object__對Python 來說有特殊含義,對於普通的變量應當避免這種命名風格。

主要包括:

  • object # public 公共的
  • __object__ # special, python system use, user should not define like it 
  • __object # private (name mangling during runtime) 私有的
  •  _object # obey python coding convention, consider it as private 保護的

以上私有和保護都不是嚴格意義的類似於Java的概念,而是Python特有的。

在本文中,我將討論以下五種下劃線模式和命名約定

  • 單前導下劃線:_var
  • 單末尾下劃線:var_
  • 雙前導下劃線:__var
  • 雙前導和末尾下劃線:__var__
  • 單下劃線:_

二、單前導下劃線:_var

Python中並沒有真正意義上的“私有”,類的屬性的的可見性取決於屬性(變量和方法)的名字。

以單下劃線開頭的屬性(例如_spam),應被當成API中非公有的部分(但是注意,它們仍然可以被訪問),一般是具體實現細節的部分修改它們時無需對外部通知,需要提供外部接口以供外部調用。在類中,帶有前導下劃線的名稱只是向其他程序員指示該屬性或方法旨在是私有的。

引用PEP-8

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

class BaseForm(StrAndUnicode):
    ...

    def _get_errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors

    errors = property(_get_errors)

該代碼片段來自Django源碼(django/forms/forms.py)。這段代碼的設計就是errors屬性是對外API的一部分,如果你想獲取錯誤詳情,應該訪問errors屬性,而不是(也不應該)訪問_get_errors方法。

(1)約定的內部變量

當涉及到變量和方法名稱時,單個下劃線前綴有一個約定俗成的含義。 它是對程序員的一個提示 ,意味着Python社區一致認爲它應該是什麼意思,但程序的行爲不受影響。下劃線前綴的含義是告知其他程序員:以單個下劃線開頭的變量或方法僅供內部使用。 該約定在PEP 8中有定義。但這不是Python強制規定的。 Python不像Java那樣在“私有”和“公共”變量之間有很強的區別。 

看看下面的例子:

class Test:
    def __init__(self):
        self.foo = 11
        self._bar = 23

如果你實例化此類,並嘗試訪問在__init__構造函數中定義的foo和_bar屬性,會發生什麼情況? 讓我們來看看:

>>> t = Test()
>>> t.foo
11
>>> t._bar
23

你會看到_bar中的單個下劃線並沒有阻止我們“進入”類並訪問該變量的值。這是因爲Python中的單個下劃線前綴僅僅是一個約定,而不是一個強制性的規則。至少相對於變量和方法名而言。

其可以直接訪問。不過根據python的約定,應該將其視作private,而不要在外部使用它們,良好的編程習慣是不要在外部使用它

(2)模塊級私有化

單下劃線常用來實現模塊級私有化,當我們使用“from mymodule import *”來加載模塊的時候,不會加載以單下劃線開頭的模塊屬性。也就是前導下劃線的確會影響從模塊中導入名稱的方式。

假設你在一個名爲my_module的模塊中有以下代碼:

# This is my_module.py:

def external_func():
    return 23

def _internal_func():
    return 42

現在,如果使用通配符從模塊中導入所有名稱,則Python不會導入帶有前導下劃線的名稱除非模塊定義了覆蓋此行爲的__all__列表,模塊或包中的__all__列表顯式地包含了他們):

>>> from my_module import *
>>> external_func()
23
>>> _internal_func()
NameError: "name '_internal_func' is not defined"

順便說一下,應該避免通配符導入,因爲它們使名稱空間中存在哪些名稱不清楚,存在重名的變量會出現混亂。 爲了清楚起見,堅持常規導入更好。

與通配符導入不同,常規導入不受前導單個下劃線命名約定的影響:

>>> import my_module
>>> my_module.external_func()
23
>>> my_module._internal_func()
42

我知道這一點可能有點令人困惑。 如果你遵循PEP 8推薦,避免通配符導入,那麼你真正需要記住的只有這個:

單個下劃線是一個Python命名約定,表示這個名稱是供內部使用的。 它通常不由Python解釋器強制執行,僅僅作爲一種對程序員的提示。

三、單末尾下劃線:var_

有時候,一個變量的最合適的名稱已經被一個關鍵字所佔用。 因此,像class或def這樣的名稱不能用作Python中的變量名稱。 在這種情況下,你可以附加一個下劃線來解決命名衝突:

>>> def make_object(name, class):
SyntaxError: "invalid syntax"

>>> def make_object(name, class_):
...     pass

總之,單個末尾下劃線(後綴)是一個約定,用來避免與Python關鍵字產生命名衝突。 PEP 8解釋了這個約定。

四、雙前導下劃線:__var

雙下劃線用在Python類變量中是Name Mangling。這種特定的行爲差不多等價於Java中的final方法(不能被擠成)和C++中的正常方法(非虛方法)。之前很多人說Python中雙下劃線開頭表示私有。這樣理解可能也不能說錯,但這不是Python設計雙下劃線開頭的初衷和目的,Python設計此的真正目的僅僅是爲了避免子類覆蓋父類的方法。

《Python學習手冊》的說明,以雙下劃線開頭的變量名,會自動擴張(原始的變量名會在頭部加入一個下劃線,然後是所在類名稱,從而包含了所在類的名稱。無法被繼承,也無法在外部訪問。必須通過改寫後的變量名或函數名來訪問。更像是私有變量和私有函數。當然也不是靜態變量。

(1)防止子類重寫父類屬性

以雙下劃線開頭並最多以一個下劃線結尾的變量或函數(例如___spam),將會被替換成_classname__spam這樣的形式,其中classname是當前類的類名。知道了這點之後,我們仍然可以訪問到python的私有函數

the Python docs

Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped. This mangling is done without regard to the syntactic position of the identifier, so it can be used to define class-private instance and class variables, methods, variables stored in globals, and even variables stored in instances. private to this class on instances of other classes.

和來自同一頁面的警告:

Name mangling is intended to give classes an easy way to define “private” instance variables and methods, without having to worry about instance variables defined by derived classes, or mucking with instance variables by code outside the class. Note that the mangling rules are designed mostly to avoid accidents; it still is possible for a determined soul to access or modify a variable that is considered private.

到目前爲止,我們所涉及的所有命名模式的含義,來自於已達成共識的約定。 而對於以雙下劃線開頭的Python類的屬性(包括變量和方法),情況就有點不同了。

雙下劃線前綴會導致Python解釋器重寫屬性名稱,以避免子類中的命名衝突。這也叫做名稱修飾(name mangling) - 解釋器更改變量的名稱,以便在類被擴展的時候不容易產生衝突。

我知道這聽起來很抽象。 因此,我組合了一個小小的代碼示例來予以說明:

class Test:
    def __init__(self):
        self.foo = 11
        self._bar = 23
        self.__baz = 23

讓我們用內置的dir()函數來看看這個對象的屬性:

>>> t = Test()
>>> dir(t)
['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__',
 '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
 '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
 '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
 '__weakref__', '_bar', 'foo']

以上是這個對象屬性的列表。 讓我們來看看這個列表,並尋找我們的原始變量名稱foo,_bar和__baz - 我保證你會注意到一些有趣的變化。

  • self.foo變量在屬性列表中顯示爲未修改爲foo。
  • self._bar的行爲方式相同 - 它以_bar的形式顯示在類上。 就像我之前說過的,在這種情況下,前導下劃線僅僅是一個約定。 給程序員一個提示而已。
  • 然而,對於self.__baz而言,情況看起來有點不同。 當你在該列表中搜索__baz時,你會看不到有這個名字的變量。

__baz出什麼情況了?

如果你仔細觀察,你會看到此對象上有一個名爲_Test__baz的屬性。 這就是Python解釋器所做的名稱修飾。 它這樣做是爲了防止變量在子類中被重寫。

讓我們創建另一個擴展Test類的類,並嘗試重寫構造函數中添加的現有屬性:

class ExtendedTest(Test):
    def __init__(self):
        super().__init__()
        self.foo = 'overridden'
        self._bar = 'overridden'
        self.__baz = 'overridden'

現在,你認爲foo,_bar和__baz的值會出現在這個ExtendedTest類的實例上嗎? 我們來看一看:

>>> t2 = ExtendedTest()
>>> t2.foo
'overridden'
>>> t2._bar
'overridden'
>>> t2.__baz
AttributeError: "'ExtendedTest' object has no attribute '__baz'"

等一下,當我們嘗試查看t2 .__ baz的值時,爲什麼我們會得到AttributeError? 名稱修飾被再次觸發了! 事實證明,這個對象甚至沒有__baz屬性:

>>> dir(t2)
['_ExtendedTest__baz', '_Test__baz', '__class__', '__delattr__',
 '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
 '__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
 '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
 '__subclasshook__', '__weakref__', '_bar', 'foo', 'get_vars']

正如你可以看到__baz變成_ExtendedTest__baz以防止意外修改:

>>> t2._ExtendedTest__baz
'overridden'

但原來的_Test__baz還在:

>>> t2._Test__baz
42

雙下劃線名稱修飾對程序員是完全透明的。 下面的例子證實了這一點:

class ManglingTest:
    def __init__(self):
        self.__mangled = 'hello'

    def get_mangled(self):
        return self.__mangled

>>> ManglingTest().get_mangled()
'hello'
>>> ManglingTest().__mangled
AttributeError: "'ManglingTest' object has no attribute '__mangled'"

名稱修飾是否也適用於方法名稱? 是的,也適用。名稱修飾會影響在一個類的上下文中,以兩個下劃線字符("dunders")開頭的所有名稱:

class MangledMethod:
    def __method(self):
        return 42

    def call_it(self):
        return self.__method()

>>> MangledMethod().__method()
AttributeError: "'MangledMethod' object has no attribute '__method'"
>>> MangledMethod().call_it()
42

這是另一個也許令人驚訝的運用名稱修飾的例子:

_MangledGlobal__mangled = 23

class MangledGlobal:
    def test(self):
        return __mangled

>>> MangledGlobal().test()
23

在這個例子中,我聲明瞭一個名爲_MangledGlobal__mangled的全局變量。然後我在名爲MangledGlobal的類的上下文中訪問變量。由於名稱修飾,我能夠在類的test()方法內,以__mangled來引用_MangledGlobal__mangled全局變量。 Python解釋器自動將名稱__mangled擴展爲_MangledGlobal__mangled,因爲它以兩個下劃線字符開頭。這表明名稱修飾不是專門與類屬性關聯的。它適用於在類上下文中使用的兩個下劃線字符開頭的任何名稱。 

(2)示例

class A(object): 
    def __method(self):
        print("I'm a method in class A")
    def method_x(self):
        print("I'm another method in class A\n")
    def method(self):
        self.__method()
        self.method_x()

class B(A):    
    def __method(self):
        print("I'm a method in class B")
    def method_x(self):
        print("I'm another method in class B\n")

if __name__ == '__main__':
    
    print("situation 1:")
    a = A()
    a.method()

    b = B()
    b.method()

    print("situation 2:")
    # a.__method()
    a._A__method() 

執行結果:

situation 1:
I'm a method in class A
I'm another method in class A

I'm a method in class A
I'm another method in class B

situation 2:
I'm a method in class A

這裏有兩個點需要注意:

A類中我們定義了__method()、method_x和method()三個方法;然後我們重新定義一個類B,繼承自A,並且在B類中覆寫(override)了其父類的__method()和method_x方法,但是從輸出結果看,B對象調用method()方法時調用了其父類A的__method()方法和自己的method_x()方法。也就是說,__method()覆寫沒有生效,而method_x()覆寫生效了。而這也正是Python設計雙下劃線開頭的唯一目的。

前面我們就說了,Python中不存在真正意義上的私有變量。對於雙下劃線開頭的方法和屬性雖然我們不能直接引用,那是因爲Python默認在其前面加了前綴_類名,所以就像situation 2下面的代碼,雖然我們不能用a直接訪問__method(),但卻可以加上前綴去訪問,即_A__method()。

Python的私有變量軋壓:

Python把以兩個或以上下劃線字符開頭且沒有以兩個或以上下劃線結尾的變量當作私有變量。私有變量會在代碼生成之前被轉換爲長格式(變爲公有)。轉換機制是這樣的:在變量前端插入類名,再在前端加入一個下劃線字符。這就是所謂的私有變量軋壓(Private name mangling)

注意:
一是因爲軋壓會使標識符變長,當超過255的時候,Python會切斷,要注意因此引起的命名衝突。
二是當類名全部以下劃線命名(如___)的時候,Python就不再執行軋壓。

具體原因:
因爲類A定義了一個私有成員函數(變量),所以在代碼生成之前先執行私有變量軋壓。軋壓之後,類A的代碼就變成這樣了:

class A(object):   
    def _A__method(self): # 這行變了
        print("I'm a method in class A")
    def method_x(self):
        print("I'm another method in class A\n")
    def method(self):
        self._A__method() # 這行變了
        self.method_x()

有點像C語言裏的宏展開。因爲在類B定義的時候沒有覆蓋__init__方法,所以調用的仍然是A.__init__,即執行了self._A__method(),自然輸出“I'm a method in class A”了。

下面的兩段代碼可以增加說服力,增進理解:

class A(object):
       def __init__(self):
              self.__private()
              self.public()
       def __private(self):
              print 'A.__private()'
       def public(self):
              print 'A.public()'
class B(A):
       def __private(self):
              print 'B.__private()'
       def public(self):
              print 'B.public()'
b = B()


A.__private()
B.public()

原因與上述相同。類 A裏的__private標識符將被轉換爲_A__private,這就是_A__private和__private消失的原因了。 

class C(A):
    def __init__(self):          # 重寫 __init__ ,不再調用 self._A__private
        self.__private()       # 這裏綁定的是 _C_private
        self.public()
    def __private(self):
        print 'C.__private()'
    def public(self):
        print 'C.public()'
c = C()


C.__private()
C.public()

可以看出重寫 __init__ ,不再調用 self._A__private,而是調用類C裏__init__函數裏綁定的_C_private。

而且在類A中也可以直接調用self._A__method(),雖然該變量未定義,但是進行命名改編時會修改。

五、雙前導和末尾下劃線:__var__

如果一個名字同時以雙下劃線開始和結束,則不會應用名稱修飾。 由雙下劃線前綴和後綴包圍的變量不會被Python解釋器修改:

class PrefixPostfixTest:
    def __init__(self):
        self.__bam__ = 42

>>> PrefixPostfixTest().__bam__
42

但是,Python保留了有雙前導和雙末尾下劃線的名稱,用於特殊用途。 前後有雙下劃線表示的是特殊函數。通常可以複寫這些方法實現自己所需要的功能。

這樣的例子有,__init__ --- 對象構造函數__call__ --- 它使得一個對象可以被調用。表示這是Python自己調用的,程序員不要調用。比如我們可以調用len()函數來求長度,其實它後臺是調用了__len__()方法。一般我們應該使用len,而不是直接使用__len__()。

我們一般稱__len__()這種方法爲magic methods(魔術方法),一些操作符後臺調用的也是也是這些magic methods,比如+後臺調用的是__add__,-調用的是__sub__,所以這種機制使得我們可以在自己的類中覆寫操作符(見後面例子)。另外,有的時候這種開頭結尾雙下劃線方法僅僅是某些特殊場景的回調函數,比如__init__()會在對象的初始化時調用,__new__()會在構建一個實例的時候調用等等。

  • 類內置函數和全局內置函數
  • 標識符等的實現細節
  • 回調函數
class CrazyNumber(object):
    def __init__(self, n): 
        self.n = n 
    def __add__(self, other): 
        return self.n - other 
    def __sub__(self, other): 
        return self.n + other 
    def __str__(self): 
        return str(self.n) 

num = CrazyNumber(10) 
print(num) # output is: 10
print(num + 5) # output is: 5
print(num - 20) # output is: 30

在上面這個例子中,我們重寫了+-操作符,將他們的功能交換了。

 

六、單下劃線:_

(1)臨時變量

按照習慣,有時候單個獨立下劃線是用作一個名字,來表示某個變量是臨時的或無關緊要的。作爲臨時性的名稱使用,但是在後面不會再次用到該名稱。這種用法在循環中會經常用到。

例如,在下面的循環中,我們不需要訪問正在運行的索引,我們可以使用“_”來表示它只是一個臨時值:

>>> for _ in range(32):
...     print('Hello, World.')

你也可以在拆分(unpacking)表達式(對元組以逗號分隔開的表達式)中將單個下劃線用作“不關心的”變量,以忽略特定的值。 同樣,這個含義只是“依照約定”,並不會在Python解釋器中觸發特殊的行爲。 單個下劃線僅僅是一個有效的變量名稱,會有這個用途而已。

在下面的代碼示例中,我將汽車元組拆分爲單獨的變量,但我只對顏色和里程值感興趣。 但是,爲了使拆分表達式成功運行,我需要將包含在元組中的所有值分配給變量。 在這種情況下,“_”作爲佔位符變量可以派上用場:

>>> car = ('red', 'auto', 12, 3812.4)
>>> color, _, _, mileage = car

>>> color
'red'
>>> mileage
3812.4
>>> _
12

(2)_代表交互式解釋器會話中上一條的執行結果

除了用作臨時變量之外,“_”是大多數Python REPL中的一個特殊變量,它表示由解釋器執行的最近一個表達式的結果。這種用法有點類似於Linux中的上一條命令的用法。只不過在在Python解釋器中表示的上一條執行的結果。這種用法首先被標準CPython解釋器採用,然後其他類型的解釋器也先後採用。

這樣就很方便了,比如你可以在一個解釋器會話中訪問先前計算的結果,或者,你是在動態構建多個對象並與它們交互,無需事先給這些對象分配名字:

>>> 20 + 3
23
>>> _
23
>>> print(_)
23

>>> list()
[]
>>> _.append(1)
>>> _.append(2)
>>> _.append(3)
>>> _
[1, 2, 3]

(3)國際化

也許你也曾看到”_“會被作爲一個函數來使用。這種情況下,它通常用於實現國際化和本地化字符串之間翻譯查找的函數名稱,這似乎源自並遵循相應的C約定。

from django.utils.translation import ugettext as _
from django.http import HttpResponse

def my_view(request):
    output = _("Welcome to my site.")
    return HttpResponse(output)

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

七、訪問控制

在Class內部,可以有屬性和方法,而外部代碼可以通過直接調用實例變量的方法來操作數據,這樣,就隱藏了內部的複雜邏輯。

但是,從前面Student類的定義來看,外部代碼還是可以自由地修改一個實例的name、score屬性

>>> bart = Student('Bart Simpson', 98)
>>> bart.score
98
>>> bart.score = 59
>>> bart.score
59

如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線__,在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問,所以,我們把Student類改一改:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print '%s: %s' % (self.__name, self.__score)

改完後,對於外部代碼來說,沒什麼變動,但是已經無法從外部訪問實例變量.__name實例變量.__score了:

>>> bart = Student('Bart Simpson', 98)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

這樣就確保了外部代碼不能隨意修改對象內部的狀態,這樣通過訪問限制的保護,代碼更加健壯。

但是如果外部代碼要獲取name和score怎麼辦?可以給Student類增加get_name和get_score這樣的方法(相當於Java的getter和setter方法):

class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

如果又要允許外部代碼修改score怎麼辦?可以給Student類增加set_score方法:

class Student(object):
    ...

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

你也許會問,原先那種直接通過bart.score = 59也可以修改啊,爲什麼要定義一個方法大費周折?因爲在方法中,可以對參數做檢查,避免傳入無效的參數

class Student(object):
    ...

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

需要注意的是,在Python中,變量名類似__xxx__的,也就是以雙下劃線開頭,並且以雙下劃線結尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量,所以,不能用__name__、__score__這樣的變量名。

有些時候,你會看到以一個下劃線開頭的實例變量名,比如_name,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視爲私有變量,不要隨意訪問”。

雙下劃線開頭的實例變量是不是一定不能從外部訪問呢?其實也不是。不能直接訪問__name是因爲Python解釋器對外把__name變量改成了_Student__name,所以,仍然可以通過_Student__name來訪問__name變量:

>>> bart._Student__name
'Bart Simpson'

但是強烈建議你不要這麼幹,因爲不同版本的Python解釋器可能會把__name改成不同的變量名。

總的來說就是,Python本身沒有任何機制阻止你幹壞事,一切全靠自覺

訪問限制

閱讀: 226345


在Class內部,可以有屬性和方法,而外部代碼可以通過直接調用實例變量的方法來操作數據,這樣,就隱藏了內部的複雜邏輯。

但是,從前面Student類的定義來看,外部代碼還是可以自由地修改一個實例的namescore屬性:

>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99

如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線__,在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問,所以,我們把Student類改一改:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

改完後,對於外部代碼來說,沒什麼變動,但是已經無法從外部訪問實例變量.__name實例變量.__score了:

>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

這樣就確保了外部代碼不能隨意修改對象內部的狀態,這樣通過訪問限制的保護,代碼更加健壯。

但是如果外部代碼要獲取name和score怎麼辦?可以給Student類增加get_nameget_score這樣的方法:

class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

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

class Student(object):
    ...

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

你也許會問,原先那種直接通過bart.score = 99也可以修改啊,爲什麼要定義一個方法大費周折?因爲在方法中,可以對參數做檢查,避免傳入無效的參數:

class Student(object):
    ...

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

需要注意的是,在Python中,變量名類似__xxx__的,也就是以雙下劃線開頭,並且以雙下劃線結尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量,所以,不能用__name____score__這樣的變量名。

有些時候,你會看到以一個下劃線開頭的實例變量名,比如_name,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視爲私有變量,不要隨意訪問”。

雙下劃線開頭的實例變量是不是一定不能從外部訪問呢?其實也不是。不能直接訪問__name是因爲Python解釋器對外把__name變量改成了_Student__name,所以,仍然可以通過_Student__name來訪問__name變量:

>>> bart._Student__name
'Bart Simpson'

但是強烈建議你不要這麼幹,因爲不同版本的Python解釋器可能會把__name改成不同的變量名。

總的來說就是,Python本身沒有任何機制阻止你幹壞事,一切全靠自覺。

最後注意下面的這種錯誤寫法

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 設置__name變量!
>>> bart.__name
'New Name'

表面上看,外部代碼“成功”地設置了__name變量,但實際上這個__name變量和class內部的__name變量不是一個變量!內部的__name變量已經被Python解釋器自動改成了_Student__name,而外部代碼給bart新增了一個__name變量。不信試試:

>>> bart.get_name() # get_name()內部返回self.__name
'Bart Simpson'

八、總結

(1)除了上述用法,下劃線還作爲函數的連接符,僅僅是一種函數名的命名方式,就如同Java的駝峯式的命名法是一樣的。

(2)python有對private的描述,但python中不存在protected的概念,要麼是public要麼就是private,但是python中的private不像C++, Java那樣,它並不是真正意義上的private,通過name mangling(名稱改編(目的就是以防子類意外重寫基類的方法或者屬性),即前面加上“單下劃線”+類名,eg:_Class__object)機制就可以訪問private了。

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

(3)

  1. 使用單下劃線(_one_underline)開頭表示方法不是API的一部分,不要直接訪問(雖然語法上訪問也沒有什麼問題)。
  2. 使用雙下劃線開頭(__two_underlines)開頭表示子類不能覆寫該方法。除非你真的知道你在幹什麼,否則不要使用這種方式。
  3. 當你想讓自己定義的對象也可以像Python內置的對象一樣使用Python內置的一些函數或操作符(比如len、add、+、-、==等)時,你可以定義該類方法。
  4. 當然還有些屬性只在末尾加了但下劃線,這僅僅是爲了避免我們起的一些名字和Python保留關鍵字衝突,沒有特殊含義。

以下是一個簡短的小結,即“速查表”,羅列了我在本文中談到的五種Python下劃線模式的含義:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章