Python物件導向: 類別class裡什麼時候該用 self?什麼放成類別等級變數?區域變數, 實例屬性, 類別屬性

加入好友
加入社群
Python物件導向: 類別class裡什麼時候該用 self?什麼放成類別等級變數?區域變數, 實例屬性, 類別屬性 - 儲蓄保險王

一、先認識三種「變數範圍」

  • 區域變數(local variable)
    • 在方法(函式)內宣告,只在該方法執行期間存在。
    • 不帶 self,也不帶類別名。
  • 實例屬性(instance attribute)
    • 以 self.xxx 存取,屬於某一個物件實例的狀態。
    • 不同實例之間彼此獨立。
  • 類別屬性(class attribute)
    • 直接寫在 class 區塊內、方法外,或以 類別名.xxx 存取。
    • 所有實例共享同一份值。

二、什麼時候要用 self?

  • 需要跨方法共享的狀態 → 用 self
    • 例如:檔案路徑、連線、設定、累計計數、快取、模型物件。
  • 屬於這個「物件個體」的資料 → 用 self
    • 每個實例不同,如每位使用者的名稱、每個紀錄器的檔案位置。

不該用 self 的時候

  • 只在單一方法中短暫使用的中間變數,請用區域變數。
  • 迴圈索引、臨時計算結果、一次性參數轉換等。

三、什麼時候用「類別屬性」?

  • 常數或預設值,所有實例都一樣,且通常不會被單一實例特別覆寫。
    • 例如:版本號、預設格式字串、全域的限制值。
  • 跨實例共享的資源或統計
    • 例如:建立了多少個實例的計數器。
  • 注意:類別屬性被「實例屬性同名」覆寫時,該實例會看到自己的值,其他實例仍用類別值。

四、最簡單的例子

例 1:區域變數 vs 實例屬性

class Greeter:
    def __init__(self, name):
        self.name = name          # 實例屬性跨方法需要每個人不同
        # 下列變數若只在 __init__ 用到就不需要 self
        greeting = f"Hello, {name}"   # 區域變數:只在這裡短暫使
        # print(greeting)  # 用完就好不必存成 self.greeting

    def say_hi(self):
        # 需要用到初始化時的 name所以用 self.name
        return f"Hi, {self.name}!"

例 2:類別屬性(所有實例共享)

class Greeter:
    default_prefix = "Hi"   # 類別屬性所有實例共用
    count = 0               # 類別屬性統計建立多少個實例

    def __init__(self, name):
        self.name = name    # 實例屬性每個人不同
        Greeter.count += 1  # 用類別名操作共享計數器

    def say(self):
        # 使用類別屬性與實例屬性
        return f"{Greeter.default_prefix}, {self.name}!"

g1 = Greeter("Alice")
g2 = Greeter("Bob")
print(Greeter.count)  # 2

輸出:

Python物件導向: 類別class裡什麼時候該用 self?什麼放成類別等級變數?區域變數, 實例屬性, 類別屬性 - 儲蓄保險王

例 3:實務常見情境(檔案處理與 logger)

from pathlib import Path
import logging

class DocumentLogger:
    # 類別屬性共用的格式與日期格式
    LOG_FORMAT = "%(asctime)s | %(levelname)s | %(name)s | %(message)s"
    DATE_FORMAT = "%Y-%m-%d %H:%M:%S"

    def __init__(self, output_file):
        # 實例屬性每個 logger 指向不同檔案
        self.file_path = Path(output_file)
        self.log_path = self.file_path.with_suffix(".log")
        self.logger = self._build_logger()

    def _build_logger(self):
        logger = logging.getLogger(f"DocumentLogger:{self.file_path.stem}")
        logger.setLevel(logging.INFO)
        logger.propagate = False
        for h in list(logger.handlers):
            logger.removeHandler(h)
            h.close()
        fh = logging.FileHandler(self.log_path, encoding="utf-8")
        fmt = logging.Formatter(self.LOG_FORMAT, datefmt=self.DATE_FORMAT)
        fh.setFormatter(fmt)
        logger.addHandler(fh)
        return logger

    def info(self, msg):
        # 用區域變數做短暫的字串拼接就好不必寫 self.msg
        line = f"[{self.file_path.name}] {msg}"
        self.logger.info(line)

五、常見誤區與反例

  • 誤區 1:為了「安全」把所有變數都變 self
    • 壞處:物件狀態臃腫、不易讀、增加耦合,還可能誤用舊值。
  • 誤區 2:應該是共享的卻放在 self
    • 例如把固定常數、格式、版本放在 self,導致每個實例各自一份,難以統一管理。
  • 誤區 3:應該是實例狀態卻當成類別屬性
    • 例如把使用者名稱放成類別屬性,會被所有實例共享,互相覆蓋。

六、決策清單(拿不準時照這個問自己)

  • 這個值會不會被其他方法使用?
    • 會 → self
    • 不會 → 區域變數
  • 這個值對每個實例是否相同?
    • 相同而且希望共享 → 類別屬性
    • 不同 → 實例屬性
  • 是否是一次性中間結果?
    • 是 → 區域變數
  • 是否需要在類別外被「所有實例」讀到同一份?
    • 是 → 類別屬性(或模組常數)

七、簡短總結

  • self:代表「這個物件的狀態」,用於跨方法共享且每個實例可能不同的資料。
  • 類別屬性:所有實例共享的常數或統計。
  • 區域變數:只在當前方法內暫用的中間值。
  • 原則:最小必要狀態。需要共享才放 self;需要共享且所有實例相同才放類別屬性;其餘用區域變數。

推薦hahow線上學習python: https://igrape.net/30afN

加入好友
加入社群
Python物件導向: 類別class裡什麼時候該用 self?什麼放成類別等級變數?區域變數, 實例屬性, 類別屬性 - 儲蓄保險王

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡

You may also like...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *