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

Ameer Abdullah

类变量 vs 实例变量:让资深开发者也中招的变异陷阱

共享的可变类属性会产生非显而易见的行为,这种情况在高级 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

两个学生共享同一个成绩列表。这就是类变量变异的陷阱。


为什么会这样

类变量定义在类对象本身,而不是实例上。当你访问 self.grades 时,Python 首先检查实例的 __dict__。如果没有找到,它就会在类中查找该属性。alice 和 bob 都会在 Student 类上找到同一个列表对象。

.append() 会修改那个共享的列表对象。所有实例都能看到这个改动。


解决方案

class Student:
    def __init__(self):
        self.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]

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  # 常量——适合作为类变量
    active_connections = 0  # 类范围计数器——如果谨慎管理则适合

    def __init__(self):
        if DatabaseConnection.active_connections >= DatabaseConnection.MAX_CONNECTIONS:
            raise RuntimeError("已达到连接限制")
        DatabaseConnection.active_connections += 1

Enter fullscreen mode Exit fullscreen mode

请注意,修改类变量应通过类名而非 self 进行,以明确意图并避免意外创建实例变量。


PyCodeIt 2.0 正式发布!

我已从头开始重建了你终极的 Python 面试沙盒。无论你是计算机科学专业的学生、数据科学毕业生,还是准备通过技术面试的开发者,Python 代码追踪都是一项经常被测试却很少练习的技能。PyCodeIt 正是为弥合这一差距而构建的,而我们的最新更新将改变一切。

pycodeit.com 上练习类变量追踪问题。