2022-01-22|閱讀時間 ‧ 約 9 分鐘

不間斷 Python 挑戰 Day 22 - 物件導向程式設計:封裝 (Encapsulation)

類別一節中,我們可以用Student類別的實體來存取類別中的name變數、score字典、以及其中的所有方法,這些可以被類別以外的程式碼所直接存取的屬性稱為公有屬性(public attribute)、可以被類別以外的程式碼所直接呼叫的方法稱為公有方法(public method)。事實上,有時我們不希望所有類別內部實作的細節被外部知道,一方面我們希望可以把類別當作一個「黑盒子」,把一個功能單位包裝起來,僅透過統一的介面被外部取用,以方便維護;另一方面我們也不希望外部的程式碼可以隨意修改類別內部的屬性或呼叫方法,導致類別資料或運算邏輯的錯誤。
Python提供物件導向程式設計的另一個特色是封裝,利用封裝的概念,我們可以設計僅供類別內部引用的私有屬性(private attribute)與私有方法(private method),可一定程度防止此類的屬性與方法被外部程式碼輕易地誤用。

私有屬性

將類別中的屬性改為私有屬性的方法是在屬性名稱前面加上雙底線「__」,我們改寫在類別一節中的Student類別,將name與score屬性改為私有屬性。
class Student:

  # 初始化
  def __init__(self, name):
    # 私有屬性
    self.__name = name
    self.__score = {"Math": 0, "Physics": 0, "Chemistry": 0}

# 建立物件
student = Student("Jack")

# 嘗試存取私有屬性
print(student.__name)
此時,若我們想從外部直接存取私有屬性student.__name與student.__score便會發生錯誤。
Traceback (most recent call last):
  File "C:\Users\wjweng\PycharmProjects\marathon_python\venv\marathon_python_day22.py", line 32, in <module>
    print(student.__name)
AttributeError: 'Student' object has no attribute '__name'
要存取私有屬性有以下幾種方式:
  • 使用公有方法設定或回傳:在類別的內部新增方法設定或回傳私有屬性。
# 回傳名字
def get_name(self):
  return self.__name

# 設定名字
def set_name(self, name):
  self.__name = name
使用方式:
# 設定名字為Rose
student.set_name("Rose")

# 印出名字
print(student.get_name())
  • 使用property()方法設定或回傳:利用上述的公有方法搭配property()方法建立新屬性,可藉由此新屬性來設定或回傳私有屬性。
# 回傳名字
def get_name(self):
  return self.__name

# 設定名字
def set_name(self, name):
  self.__name = name

name = property(get_name, set_name)
使用方式:
# 設定名字為Jack
student.name = "Jack"

# 印出名字
print(student.name)
  • 將get_name()與set_name()方法改為相同的名稱name(),使用裝飾器@property將name()轉換為可讀取的屬性、使用裝飾器@name.setter來設定名字。
@property
def name(self):
  return self.__name
@name.setter
def name(self, name):
  self.__name = name
使用方式:
# 設定名字為Jack
student.name = "Jack"

# 印出名字
print(student.name)
然而,這個__name屬性到哪裡去了呢?我們可以用物件的__dict__屬性來看一下,__dict__是用來儲存物件屬性的一個字典,其鍵為屬性名、值為屬性的值。
print(student.__dict__)
可以看到這個私有屬性還在,只是被改名為_Student__name。
{'_Student__name': 'Jack', '_Student__score': {'Math': 0, 'Physics': 0, 'Chemistry': 0}}
因此,我們還是可以透過此名稱來存取私有屬性,但至少可以防止私有屬性被外部程式碼意外地存取或修改。
print(student._Student__name)
執行結果:
Jack

私有方法

將類別中的方法改為私有方法的方式和私有屬性相同,是在方法的名稱前面加上雙底線「__」,延續上方的範例,我們將原有的add_subject方法改為私有方法__add_subject,並在set_score方法中呼叫它。
class Student:

  # 初始化
  def __init__(self, name):
    # 私有屬性
    self.__name = name
    self.__score = {"Math": 0, "Physics": 0, "Chemistry": 0}

  # 私有方法
  def __add_subject(self, subject):
    self.__score[subject] = 0

  # 公有方法
  def set_score(self, subject, score):
    if subject not in self.__score:
      self.__add_subject(subject)
    self.__score[subject] = score

  def get_subject(self):
    for key in self.__score:
      print(key)

# 建立物件
student = Student("Jack")

# 新增科目
student.__add_subject("Art")
此時,若我們想從外部直接呼叫__add_subject方法便會發生錯誤。
Traceback (most recent call last):
  File "C:\Users\wjweng\PycharmProjects\marathon_python\venv\marathon_python_day22.py", line 40, in <module>
    student.__add_subject("Art")
AttributeError: 'Student' object has no attribute '__add_subject'
透過set_score方法,我們可以在類別內部呼叫__add_subject方法。
# 登錄分數
student.set_score("Art", 80)

# 取得分數
student.get_score("Art")
執行結果:
80
然而,和私有屬性同樣的方式,我們依然可以透過被改名後的方法名稱,從外部直接呼叫私有方法。
# 呼叫私有方法
student._Student__add_subject("Music")

# 取得現有科目
student.get_subject()
執行結果:
Math
Physics
Chemistry
Art
Music

程式範例

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.