类变量 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 上练习类变量追踪问题。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.