iT邦幫忙

2022 iThome 鐵人賽

DAY 6
0

昨天講到封裝的一個重要觀念「保護層級」,筆者稱之為三個p's(或P's):public(公開), protected(保護), 和private(私有)。今天將更深入說明。

先複習這三個p's的意義:

public(公開):

  • 完全開放,物件可在外部自由存取,封裝層級最低。

protected(保護):

  • 僅物件及其子孫的內部才可存取,封裝層級中等。

private(私有):

  • 只有類別(昨天誤作物件)自身內部方可存取,封裝層級最高。

公開屬性:可以外部存取

  • 上面說的「內部」和「外部」,請看以下程式(此編輯器的markdown,程式區塊好像沒有加行號功能):

    class Tree():
        def __init__(self, breed: str, age: int):
            self.breed = breed
            self.age = age
    
    
    laozi = Tree('cedar', 2593)   # 老子神木。
    
    print('\n以下都算是「外部存取」:')
    print('.透過object.attribute(物件.屬性)的表示法「取得」屬性。')
    print(f"  laozi's breed: {laozi.breed}")   # 取值。
    
    print('\n.透過object.attribute(物件.屬性)的表示法「修改」屬性。')
    laozi.breed = 'phoebe'   # 賦值。
    print(f"  laozi's breed: {laozi.breed}")
    

    輸出:
    https://ithelp.ithome.com.tw/upload/images/20220921/201484857P76qN0dOr.png

  • 其中laozi = Tree('cedar', 2593)意思是以Tree()類別為藍本,建立一棵名為laozi(老子神木)的樹,樹種為cedar,樹齡2593歲。這棵神木就是一個物件(an object or an instance)。

  • print(f" laozi's breed: {laozi.breed}")這行,就是上篇提到的「物件.屬性」表示式,以取得laozi物件的屬性breed的值。

  • laozi.breed = 'phoebe'也很容易理解,很明顯是透過「物件.屬性」加上賦值,來修改laozi的屬性,將原來的'cedar'(雪松)改為'phoebe'(楠木)。

  • 以上的「物件.屬性」表示方式,就是筆者說的「外部存取」。下面的截圖標明了何者是物件,何者是屬性:
    https://ithelp.ithome.com.tw/upload/images/20220921/20148485R6j98YnMjy.png

  • 別忘了之前所講,Python類別(物件)內的屬性,保護層級為「公開」(public,名稱無前綴底線)或「保護」(protected,屬性名稱有一條前綴底線)者,才可以透過「物件.屬性」表示式在外部存取。

  • 再度提醒:Python類別中保護層級的屬性,只是「約定俗成」:大家有個默契,前綴一條底線的屬性就不要用「物件.屬性」的方式修改了。可這條前綴底線並無實質保護效果,類別使用者真要修改Python完全不加阻擋,有點防君子不防小人味道。筆者當然不是在暗示,修改了保護層級屬性,您就是「小人」。絕無此意。

  • 有些人會將「前綴一條底線」的屬性也說成是「私有」而完全忽略「保護」這個層級。筆者認為這會造成混淆,所以還是採用三分法。不過由於Python的保護層級實際上沒有保護力,所以往後盡量不提這層,重點放在無前綴底線的「公開」和兩條(注意是兩條不是一條)底線的「私有」兩者。

  • 至於「方法」,也和屬性一樣,只要是公開層級的方法,都可用「物件.方法」的表示式呼叫。當然,要呼叫方法,就和呼叫一般函數一樣,方法名稱後面要加小括號,例如應寫tree.get_breed()而不是tree.get_breed

私有屬性:不可外部存取

  • Python類別中的屬性,如果在其名稱的前面加兩條底線(註1),就自然變成「私有」(private)。這些屬性不能通過「物件.屬性」表示式存取:

    class Tree():
        def __init__(self, breed: str, age: int):
            self.breed = breed   # self.breed is a public attribute
            self.__age = age     # self.__age is a private attribute
    
    
    laozi = Tree('cedar', 2593)   # 建立物件。
    print('\n.無法透過object.attribute表示法取得樹齡。')
    print(laozi.__age)      # 跑到這行時會出錯。
    

    輸出:
    https://ithelp.ithome.com.tw/upload/images/20220921/20148485ohc7BQMyUx.png

  • 物件欲存取私有屬性,唯有透過類別提供的公開方法。萬一類別沒有提供,就無法存取了:

    class Tree():
        def __init__(self, breed: str, age: int):
            self.breed = breed    # public attribute
            self.__age = age      # private attribute
    
        def get_age(self) -> int:  # public method
            return self.__age
    
        def set_age(self, age: int) -> None:  # public method
            self.__age = age
    
    
    laozi = Tree('cedar', 2593)      # 建立物件。
    print('\n.呼叫類別內部提供的方法「取得」物件的private屬性。')
    print(f'  {laozi.get_age()=}')   # 取值。
    print('\n.呼叫類別內部提供的方法「修改」物件的private屬性。')
    laozi.set_age('2704')            # 賦值。
    print(f'  {laozi.get_age()=}')
    

    輸出:
    https://ithelp.ithome.com.tw/upload/images/20220921/20148485KLtAHpFQHR.png

  • get_age()set_age()這兩個公開的方法,是定義在類別之內,即是筆者所說的「內部存取」。這些類別內部方法,一方面可以存取私有屬性;另一方面,由於方法本身是公開層級,可以讓依該類別為模板而建立的物件,遵從「物件.方法」的表示式呼叫,從而存取私有屬性。

註1: 「前綴兩條底線的是私有屬性」的說法,其實不夠精準。Python私有屬性的正確定義是:「前綴雙底線而無後綴雙底線」。前後都有雙底線者是另一回事,至於是怎樣一回事,在此賣個關子,明天揭曉。


上一篇
3個P's:公開、保護、私有
下一篇
Python也有Magic Johnson?
系列文
Oops! OOPP: An Introduction to Object-Oriented Programming in Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言