在 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.__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
近期留言