问题: 什么是实例属性和类属性,两者的区别是什么?

👇 点击展开/折叠 面试背诵模板(建议先背这个) 类属性定义在类体内、方法外,被所有实例共享,属于类本身。实例属性定义在 __init__ 方法内通过 self 赋值,每个实例独立存储。访问优先级:实例属性 > 类属性。修改类属性必须通过类名(Class.attr)才会影响所有实例;通过实例给类属性赋值不会修改类属性,而是在该实例上新建同名实例属性,遮蔽类属性。类属性通常用作常量或共享状态,实例属性存储对象特有数据。
---

核心区别速览

维度 类属性 实例属性
定义位置 类体内,方法外 __init__ 内,self.xxx
归属 类本身 具体对象
内存分配 仅一份(类命名空间) 每对象一份(对象 __dict__)
访问方式 类名.属性 或 实例.属性 必须 实例.属性(或 self.属性)
修改影响 影响所有未遮蔽该属性的实例 仅影响当前实例

属性查找机制与遮蔽陷阱(高频考点)

Python 属性访问遵循”就近原则”:实例.属性 会先查找实例自身的 __dict__,若未找到则向上查找类及父类的 __dict__。关键陷阱:当执行 d1.species = "新物种" 时,Python 不会修改类属性,而是在实例 __dict__ 中插入新键值对。此后 d1.species 优先命中实例属性,类属性被”遮蔽”;而 d2.species 仍指向类属性。只有通过 Dog.species = "新物种" 才能真正修改类属性。

1
2
3
4
5
6
7
class Dog:
species = "犬科" # 类属性

d1 = Dog()
d1.species = "新犬种" # ❌ 新建实例属性,并非修改类属性
print(d1.species) # 新犬种(实例属性遮蔽)
print(Dog.species) # 犬科(类属性未变)

可变类型类属性的共享风险(进阶陷阱)

若类属性指向可变对象(如列表、字典),通过实例调用其修改方法(如 append、update)会直接修改共享对象,影响所有实例。这与”赋值”行为不同:赋值 obj.attr = [] 是在实例上新建属性,属于遮蔽;而调用方法 obj.attr.append() 是原地修改,属于共享。

1
2
3
4
5
6
7
class Student:
courses = [] # 共享列表

s1 = Student()
s2 = Student()
s1.courses.append("数学") # 原地修改,影响所有实例
print(s2.courses) # ['数学'](共享)

典型应用场景与设计选择

  • 类属性:存储不随实例变化的元数据(如学校名称、默认配置常量)、统计实例数量的计数器(cls.count += 1),或预设的可选方案(如预定义的难度等级)。
  • 实例属性:存储对象的唯一标识(如 name、id)、动态状态(如 age、score)、以及依赖外部输入的可变数据。