Class vs Instance Variables: The Mutation Trap That Catches Experienced Developers 的封面圖片

Ameer Abdullah

Class vs Instance Variables: The Mutation Trap That Catches Experienced Developers

共享的可變類別屬性會產生不明顯的行為,這種情況經常出現在資深 Python 面試中


class Student:
    grades = []

    def add_grade(self, grade):
        self.grades.append(grade)

alice = Student()
bob = Student()

alice.add_grade(90)
bob.add_grade(85)

print(alice.grades)
print(bob.grades)

Enter fullscreen mode Exit fullscreen mode

你認為這段程式碼會輸出什麼?

輸出結果:

[90, 85]
[90, 85]

Enter fullscreen mode Exit fullscreen mode

兩個學生共用同一個 grades 清單。這就是類別變數的變動陷阱。


為什麼會發生這種情況

類別變數定義在類別物件本身,而非實例上。當你存取 self.grades 時,Python 會先檢查實例的 __dict__。找不到時,才會向上查找類別中的屬性。alicebob 都會找到同一個位於 Student 類別上的清單物件。

.append() 會變動這個共享的清單物件。所有實例都能看到這個變動。


解決方法

class Student:
    def __init__(self):
        self.grades = []  # instance variable, created fresh for each instance

    def add_grade(self, grade):
        self.grades.append(grade)

alice = Student()
bob = Student()

alice.add_grade(90)
bob.add_grade(85)

print(alice.grades)
print(bob.grades)

Enter fullscreen mode Exit fullscreen mode

輸出結果:

[90]
[85]

Enter fullscreen mode Exit fullscreen mode

將清單初始化移到 __init__ 中,可為每個實例建立新的清單。


實例變數遮蔽

class Counter:
    count = 0

    def increment(self):
        self.count += 1

c1 = Counter()
c2 = Counter()

c1.increment()
c1.increment()
c2.increment()

print(Counter.count)
print(c1.count)
print(c2.count)

Enter fullscreen mode Exit fullscreen mode

輸出結果:

0
2
1

Enter fullscreen mode Exit fullscreen mode

self.count += 1 等同於 self.count = self.count + 1。讀取 self.count 會找到類別變數(0)。賦值則會在 self 上建立新的實例變數,遮蔽類別變數。類別變數維持為 0。

這與可變類別屬性的情況不同。整數是不可變的,因此 += 會建立新物件並綁定到實例,而不是變動類別層級的物件。


何時適合使用類別變數

類別變數適用於:

  • 所有實例共享的常數

  • 適用於所有實例的配置值

  • 真正追蹤類別範圍狀態的計數器(需小心管理)

class DatabaseConnection:
    MAX_CONNECTIONS = 10  # constant — appropriate as class variable
    active_connections = 0  # class-wide counter — appropriate if managed carefully

    def __init__(self):
        if DatabaseConnection.active_connections >= DatabaseConnection.MAX_CONNECTIONS:
            raise RuntimeError("Connection limit reached")
        DatabaseConnection.active_connections += 1

Enter fullscreen mode Exit fullscreen mode

請注意,修改類別變數時應透過類別名稱而非 self 來進行,以明確表達意圖並避免意外建立實例變數。


PyCodeIt 2.0 正式推出!

我已從頭重建你的終極 Python 面試沙盒。無論你是計算機科學學生、資料科學畢業生,還是準備通過技術面試的開發者,Python 程式碼追蹤都是一項經常被測試卻很少練習的技能。PyCodeIt 專門為彌補這個差距而建,而我們的最新更新改變了一切。

請至 pycodeit.com 練習類別變數追蹤問題。