在python中底線underscore (_)是非常重要的一個命名方法,依照在命名中的位置主要可以分成四種:
- _single_leading_underscore
- single_trailing_underscore_
- __double_leading_underscore
- __double_leading_and_trailing_underscore__
以下簡單介紹下不同點以及使用時機
1. _single_leading_underscore
在名稱前面加一個underscore主要的用意是表示這個名稱具有受保護(protected)的屬性,這在python中就是一個慣例暗示不要在類別的外面存取帶有這種屬性的物件。
class stock:
def __init__(self, no, price) -> None:
self.no = no
self._price = price
if __name__ == "__main__":
a = stock(2353, 549)
print(a.price)
"""
Traceback (most recent call last):
...
AttributeError: 'stock' object has no attribute 'price'
"""
然而這種命名方法是否又與private variables有關聯,實則不然在python中並不存在只能從物件實例中存取的private variables,這點在python official document 中有記載,那受保護protected的用意究竟是甚麼,其實就是要防止被意外存取的屬性,然而防止意外存取不代表無法存取如下。
class stock:
def __init__(self, no, price) -> None:
self.no = no
self._price = price
if __name__ == "__main__":
a = stock(2353, 549)
print(a._price) # 549
另一方面,必須注意的是使用_single_leading_underscore此種命名方法會無法被外部import
# test.py
def public_func():
return 'public'
def _protected_func():
return 'Protected'
from test import *
print(public_func()) #public
print(_protected_func())
"""
Traceback (most recent call last):
...
NameError: name '_protected_func' is not defined
"""
但如果在來源檔案加入__all__即可成功import
# test.py
def public_func():
return 'public'
def _protected_func():
return 'Protected'
__all__ = ['public_func', '_protected_func']
或者是直接import也可成功
from test import _protected_func, public_func
print(public_func()) # public
print(_protected_func()) # Protected
2. single_trailing_underscore_
在名稱後面加上單底線underscore的命名方法目的是避免與python keyword產生碰撞。
import keyword
print('class' in keyword.kwlist) # True
print('class_' in keyword.kwlist) # False
3. __double_leading_underscore
前面提到python內沒有private私用屬性,而__double_leading_underscore這種命名方法,可以發現有兩種用途一種是避免意外存取(但不建議因為會發現很多爭議,且社群文化不推崇),另一種則是避免在子類別繼承時發生名稱衝突(name clashes)。
class stock:
def __init__(self, no, price, share) -> None:
self.no = no
self.price = price
self.share = share
self.total()
def total(self) -> int:
self.money = self.price * self.share
class US_stock(stock):
def total(self, US) -> int:
self.money = self.price * self.share * US
if __name__ == "__main__":
TW_TSMC = stock(2353, 549, 1)
TSMC_ADR = US_stock(2353, 111.7, 1)
"""
Traceback (most recent call last):
...
self.total()
TypeError: total() missing 1 required positional argument: 'US'
"""
上例執行會發現overriding total method失敗,因為US_stock繼承stock中__init__會因為建構實例時找不到total所需的參數US而報錯。修正方式如下:
class stock:
def __init__(self, no, price, share) -> None:
self.no = no
self.price = price
self.share = share
self.__total()
def total(self) -> int:
self.money = self.price * self.share
__total = total
class US_stock(stock):
def total(self, US) -> int:
self.money = self.price * self.share * US
if __name__ == "__main__":
TW_TSMC = stock(2353, 549, 1)
TSMC_ADR = US_stock(2353, 111.7, 1)
print(TW_TSMC.money)
TSMC_ADR.total(28)
print(TSMC_ADR.money)
利用__命名方法將可以觸發python編譯器執行__total轉換成_stock__total,該技術稱為名稱重整(name mangling)。
4. __double_leading_and_trailing_underscore__
這種命名方法只應該留給python內建的methods或者variables,沒甚麼好說的。
以上四種命名方式都可以在PEP8文件內看到解說。
總結:
- python其實是沒有private屬性的,雖然可以利用名稱命名達到類似的作用,但都只能避免意外存取而不能防止存取。
- 四種利用underscore的命名方式其實都有它明確的意義不應該隨便使用。