Python 命名與Underscore



在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文件內看到解說。


總結:

  1. python其實是沒有private屬性的,雖然可以利用名稱命名達到類似的作用,但都只能避免意外存取而不能防止存取。
  2. 四種利用underscore的命名方式其實都有它明確的意義不應該隨便使用。
#Python







你可能感興趣的文章

3. 優美地定義 React 型別

3. 優美地定義 React 型別

Colab 中開啟 Tensorboard

Colab 中開啟 Tensorboard

第二章:7 撰寫布林通道突破策略

第二章:7 撰寫布林通道突破策略






留言討論