iT邦幫忙

2022 iThome 鐵人賽

DAY 18
0

假如今晚沒有夢到更多封裝主題,本篇應該是這條物件導向大支柱的最後一講了。


  • 前面幾篇談到:屬性(attributes)如按類別/物件層面區分,有類別屬性(class attributes)和實例屬性(instance attributes)兩種。同樣地,方法(methods)也有類別方法class methods和實例方法instance methods兩種。
  • 其實,除了class methods和instance methods,Python還有第三種方法,就是今天要介紹的static methods,中文稱為「靜態方法」(註1)。
  • 靜態方法是指某些在邏輯上和類別相關,但又用不到類別任何屬性(不管是類別屬性或實例屬性都用不到)的方法。

實例說明

  • 我們在做某個專案。為了需要撰寫get_total_species()get_total_trees()兩個函數,分別取得全球的樹種數及全球有多少棵樹。先不管函數內用甚麼方式取得,這不是今天要說的重點。

  • 另外又寫了一個通用型的判斷樹木健康程度的函數get_health_status(),傳入某些病菌的單位數量,傳回樹木健康程度字串。請別和筆者爭辯:不可能用同一標準去衡量不同樹木的健康狀況。筆者當然知道。這個方法只為程式講解而假設出來,和植物學專業毫無關係。

  • 這三個函數當然可以成為獨立於任何類別之外的普通函數。但我們再想,專案中本來就有一個Tree()類別,也許把所有和「樹」有關的函數通通納入Tree()類別內,更符合物件導向的精神

  • 不過,這三個函數其實並沒有使用任何Tree()類別內的資源(即屬性/方法),在類別中的地位較為特殊。

  • 這三個就是靜態方法

  • 類別的設計如下,略去了一些和靜態方法無關的code:

    class Tree():
        __count = 0         
    
        def __init__(self, breed: str, age: int):   # constructor
            self.__breed = breed
            self.__age = age
            Tree.__count += 1
    
        @classmethod
        @property
        def count(cls) -> int:
            '''The __count property(getter).'''
            return cls.__count
    
        @property
        def breed(self) -> str:
            '''The breed property(getter).'''
            return self.__breed
    
        @property
        def age(self) -> int:   
            '''The age property(getter).'''
            return self.__age
    
        @staticmethod      # static method要加這個decorator。
        def get_total_species():   # 這個就是static method。
            # 經過某些計算,或者連上某個API網站取得全球總樹種數目。
            total_species = 73_000  # 這是假設值。
            return total_species
    
        @staticmethod
        def get_total_trees():   # 注意:static method沒有self或cls參數。
            # 經過某些計算,或者連上某個API網站取得全球總共有多少棵樹。
            total_trees = 3_040_000_000_000  # 這是假設值。
            return total_trees
    
        @staticmethod
        def get_health_status(germs):   # 這個就是static method。
            health_table = ((0, 1_500, 'healthy'), (1_501, 30_000, 'infected'), (30_000, 100_000, 'seriously ill'), (100_001, 9**999, 'dying'))
    
            for min_, max_, status in health_table:
                if min_ <= germs <= max_:
                    return status
    
  • 測試程式:

    print(f'{Tree.get_total_species()=:,}')
    print(f'{Tree.get_total_trees()=:,}')
    print(f'{Tree.get_health_status(50_000)=}')
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221003/20148485hXgHOYtqLx.png

靜態方式的特性

  • 靜態方法通常會以@staticmethod裝飾器包裝。這個裝飾器和之前講過的@propertyclassmethod一樣,都是Python內設,直接拿來用就行,不必import甚麼模組。
  • 類別.方法()物件.方法()兩種方式都可以呼叫靜態方法。不過筆者認為類別.方法()比較合理。靜態方法本來就屬於整個類別,和物件無關,即使未建立任何物件也可以使用。
  • 靜態方法和非靜態方法相比,語法上有一個很大的不同:靜態方法的第一個參數既不是self也不是cls。事實上靜態方法根本「不能有selfcls。原因為:static methods不會接收隱藏的第一個參數
  • 正因其參數列沒有selfcls,靜態方法無法存取類別內的任何屬性,也不能呼叫類別的任何方法,連其他靜態方法也沒法呼叫。靜態方法是「獨善其身」。
  • 有此特性,靜態方法某種程度和其所屬類別保持一點疏離,但又隱然和類別構成有一個有機組合,滿有「若即若離」意味,儼然紅樓夢中的「檻外人」妙玉。
  • 筆者推測,Python的standard libraries可能大量使用static methods。

「封裝」最後整理

  • Python的封裝,依保護層級分,屬性(attributes)和方法(methods)均有:
    • 公開(private)
    • 保護(protected)
    • 私有(private)
      三個層級。其中保護級為約定俗成,未在語法層面實際支援。
  • 即使是私有等級,也可以經由「後門」破解,直接用物件.屬性存取。但Guido van Rossum警告後果自負。
  • attributes請盡量設為private,再以property包裝。因為property骨子裡是方法,是方法就可以下任何邏輯判斷以資檢誤和實作其他功能,屬性(變數)當然做不到。
  • 可以利用dataclass自動產生一些boilerplate code,節省人工作業。
  • 如依類別/物件這個層面區分:
    • 屬性(attributes)有兩種:
      1. 類別屬性(class attributes)
      2. 實例屬性(instance attribues)
    • 方法(methods)則有三種:
      1. 類別方法(class methods, 第一個參數為cls)
      2. 實例方法(instance methods, 第一個參數為self)
      3. 靜態方法(static methods, 「也無風雨也無晴」,既無cls也無self)

註1: C++, Java, C#等物件導向程式語言也有static methods(或稱static member function)。不過意義和Python的static methods不盡相同。


上一篇
To Self, or Not To Self: That Is the Question
下一篇
OO第二大支柱Inheritance:起手式
系列文
Oops! OOPP: An Introduction to Object-Oriented Programming in Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言