在 Python 的物件導向設計中,類別變數是屬於類別本身的屬性,所有實例理論上會共享它。
以圓周率 PI 為例,它是一個適合作為類別層屬性的常數。
但是存取它的方式有很多種,常見的有三種:
self.PI__class__.PI(或硬寫ClassName.PI)self.__class__.PI
這三種方法的差別主要體現在 屬性查找路徑 和 修改時的影響範圍。
範例類別
class Circle:
    PI = 3.14159  # 類別變數(所有實例共享)
    def __init__(self, radius):
        self.radius = radius
    
    def area_with_self(self):
        return self.radius ** 2 * self.PI
    def area_with_class(self):
        return self.radius ** 2 * self.__class__.PI  # 動態取得類別屬性情境對比範例
c1 = Circle(2)
c2 = Circle(2)
# 在 c1 上建立實例自己的 PI 屬性
c1.PI = 3.0
print(c1.area_with_self())   # 12.0 → 用到 c1 自己的 PI
print(c2.area_with_self())   # 12.56636 → 用到類別層的 PI
print(c1.area_with_class())  # 12.56636 → 直接找類別層 PI(忽略實例上的 PI)
print(c2.area_with_class())  # 12.56636
# 修改類別層 PI
c1.__class__.PI = 3.0  #Circle.PI = 3.0
print(c1.area_with_class())  # 12.0 → 類別 PI 改了,全部實例受影響
print(c2.area_with_class())  # 12.0輸出:

用 self.PI、self.__class__.PI、Circle.PI 
三種存取類別變數的方法:
# %%
class Circle:
    PI = 3.14159  # 類別變數(所有實例共享)
    def __init__(self, radius):
        self.radius = radius
    
    # 1️⃣ 用 self.PI
    def area_with_self(self):
        return self.radius ** 2 * self.PI
    # 2️⃣ 用 self.__class__.PI
    def area_with_class(self):
        return self.radius ** 2 * self.__class__.PI
    # 3️⃣ 用 Circle.PI(硬指定父類別)
    def area_with_classname(self):
        return self.radius ** 2 * Circle.PI
class SpecialCircle(Circle):
    PI = 3.14  # 子類別改 PI 值
# 測試
c = SpecialCircle(2)
print("=== 預設情況(無實例覆蓋) ===")
print(c.area_with_self())       # 12.56 → 用子類別 PI(3.14)
print(c.area_with_class())      # 12.56 → 用子類別 PI(3.14)
print(c.area_with_classname())  # 12.56636 → 固定用父類別 PI(3.14159)
print("\n=== 實例覆蓋 PI ===")
c.PI = 3  # 在實例上新增同名屬性,覆蓋類別變數
print(c.area_with_self())       # 12 → 用實例 PI(3.0)
print(c.area_with_class())      # 12.56 → 仍用子類別 PI(3.14),不受實例覆蓋影響
print(c.area_with_classname())  # 12.56636 → 仍固定用父類別 PI(3.14159)輸出結果:

三種方式差異表

屬性查找順序圖:

self.__class__. 與另外兩種 (self.、ClassName.) 在實務中會有明顯區別的例子。
🔹 範例 1:繼承多型差異(self.__class__ vs ClassName)
# %%
class Circle:
    PI = 3.14159
    def __init__(self, radius):
        self.radius = radius
    def area_with_classname(self):
        return self.radius ** 2 * Circle.PI   # 固定父類別 PI
    def area_with_self_class(self):
        return self.radius ** 2 * self.__class__.PI  # 動態取當前類別 PI
class SpecialCircle(Circle):
    PI = 3.14  # 略微不同的 PI
c = SpecialCircle(2)
print(c.area_with_classname())   # 12.56636 → 用的是 Circle.PI(父類別值)
print(c.area_with_self_class())  # 12.56    → 用的是 SpecialCircle.PI(子類別值)輸出:

差異重點:
Circle.PI寫死了父類別,子類別PI再怎麼改,方法也不會用到它self.__class__.PI會依照當前實例的實際類別抓對應的值,所以繼承場景更靈活- 如果將來改類別名稱,也不必全局替換程式碼
 
🔹 範例 2:實例覆蓋差異(self. vs self.__class__.)
class Circle:
    PI = 3.14159
    def __init__(self, radius):
        self.radius = radius
    def area_with_self(self):
        return self.radius ** 2 * self.PI  # 先找實例屬性
    def area_with_self_class(self):
        return self.radius ** 2 * self.__class__.PI  # 永遠找類別層
c1 = Circle(2)
c2 = Circle(2)
c1.PI = 3.0  # 只針對 c1 建立 PI 實例屬性
print(c1.area_with_self())        # 12.0   → 用 c1 自己的 PI
print(c2.area_with_self())        # 12.56636 → 用類別層的 PI
print(c1.area_with_self_class())  # 12.56636 → 忽略 c1 的 shadow 屬性,取類別層值輸出:

差異重點:
self.PI→ 容易被實例自己的屬性遮蔽(shadowing)self.__class__.PI→ 永遠取當前類別的屬性,不會被實例屬性干擾
✅ 為什麼推薦 self.__class__. 存取類別變數
- 繼承安全 → 子類別覆寫同名屬性會自動適配
 - 名稱變更免煩惱 → 改類別名稱不必全局替換程式碼
 - 避免 shadowing 問題 → 不怕實例意外創建同名屬性干擾讀值
 
圖解:屬性查找與修改影響
以下示意圖展示了三種寫法在查找與修改上的行為差異:
self.PI
   │
   ├─> [實例屬性字典] → 找到就用
   │
   └─> [類別屬性字典] → 找不到才用
 ClassName.PI / __class__.PI
   │
   └─> [指定類別屬性字典] → 忽略實例屬性  
 self.__class__.PI
   │
   ├─> self.__class__(實例的實際類別)
   └─> [該類別屬性字典] → 忽略實例屬性實務建議
- 允許個別覆蓋的情境 → 用 
self.- 適合「預設共享,但允許部分實例自訂」
 
 - 需要全域固定值 → 用 
self.__class__.(推薦)- 適合繼承結構,子類別依然能拿到自己定義的值
 
 - 快速寫固定類別的值 → 用 
ClassName.或__class__.- 僅在沒有繼承或改類名風險時使用
 
 
💡 小技巧
- 在類別方法 (
@classmethod) 內,要存取類別變數時使用cls.PI,這就是類別方法的等價安全寫法 - 在實例方法內若想安全取類別變數,推薦 
self.__class__.PI 
![Python:  pandas.Series如何只保留str,去除重複值?#isinstance(x:Any, str) -> bool #.drop_duplicates() #Series.apply( function )逐元素應用function運算 #DataFrame.apply( function )逐Series應用function運算 .drop_duplicates() 跟.unique()有何差別? df.drop_duplicates() 等效於 df[~df.duplicated()] Python:  pandas.Series如何只保留str,去除重複值?#isinstance(x:Any, str) -> bool #.drop_duplicates() #Series.apply( function )逐元素應用function運算 #DataFrame.apply( function )逐Series應用function運算 .drop_duplicates() 跟.unique()有何差別? df.drop_duplicates() 等效於 df[~df.duplicated()]](https://i1.wp.com/savingking.com.tw/wp-content/uploads/2024/11/20241123194900_0_5218de.png?quality=90&zoom=2&ssl=1&resize=350%2C233)









近期留言