在Python面向对象编程体系中,类属性和实例属性是两种核心的属性类型,它们的定义方式、存储逻辑和使用场景都有明显差异,理解这些差异是写出规范面向对象代码的基础。

类属性与实例属性的基本定义
类属性是在类定义阶段直接声明的属性,不属于任何一个具体的实例,而是属于类本身。实例属性则是在类的实例初始化过程中,通过self关键字或者实例对象动态添加的属性,每个实例的实例属性都是独立存储的。
我们可以通过下面的代码直观看到两者的定义方式:
class Student:
# 类属性,属于Student类本身
school_name = "阳光中学"
def __init__(self, name, age):
# 实例属性,属于当前实例self
self.name = name
self.age = age
# 创建两个实例
stu1 = Student("张三", 16)
stu2 = Student("李四", 17)
两者的核心区别
1. 存储位置不同
类属性存储在类的__dict__属性中,而实例属性存储在实例的__dict__属性中。我们可以通过代码验证这一点:
# 查看类属性存储位置
print(Student.__dict__)
# 输出中会包含'school_name': '阳光中学'的键值对
# 查看实例属性存储位置
print(stu1.__dict__)
# 输出为{'name': '张三', 'age': 16}
print(stu2.__dict__)
# 输出为{'name': '李四', 'age': 17}
2. 共享性不同
类属性是所有实例共享的,修改类属性会影响所有实例的访问结果;实例属性是每个实例独有的,修改一个实例的实例属性不会影响其他实例。
# 访问类属性,所有实例结果一致 print(stu1.school_name) # 输出:阳光中学 print(stu2.school_name) # 输出:阳光中学 # 修改类属性 Student.school_name = "实验中学" print(stu1.school_name) # 输出:实验中学 print(stu2.school_name) # 输出:实验中学 # 修改实例属性,仅影响当前实例 stu1.name = "张小三" print(stu1.name) # 输出:张小三 print(stu2.name) # 输出:李四
3. 访问优先级不同
当通过实例访问属性时,Python会先在实例的__dict__中查找,如果找不到才会去类的__dict__中查找。如果实例定义了和类属性同名的实例属性,会优先访问实例属性。
# 给stu1添加和类属性同名的实例属性 stu1.school_name = "私立中学" print(stu1.school_name) # 输出:私立中学,优先访问实例属性 print(stu2.school_name) # 输出:实验中学,stu2没有同名实例属性,访问类属性
常见使用场景
类属性的适用场景
- 存储所有实例共享的常量数据,比如上述的学校名称,所有学生都属于同一个学校,不需要每个实例单独存储一份。
- 统计类的实例数量,比如给类添加一个
count类属性,每创建一个实例就自增1。
下面是统计实例数量的示例:
class Student:
school_name = "阳光中学"
# 类属性,统计实例数量
count = 0
def __init__(self, name, age):
self.name = name
self.age = age
# 每创建一个实例,类属性count自增1
Student.count += 1
stu1 = Student("张三", 16)
stu2 = Student("李四", 17)
print(Student.count) # 输出:2
实例属性的适用场景
- 存储每个实例独有的特征数据,比如学生的姓名、年龄,每个学生的这些信息都是不同的,必须作为实例属性存储。
- 存储和单个实例相关的状态数据,比如学生的考试成绩,不同学生的成绩相互独立。
常见误区提醒
很多开发者会误通过实例修改类属性,比如执行stu1.school_name = "新学校",这种操作并不会修改类属性,只会给stu1添加一个同名的实例属性,不会影响其他实例和类本身的属性值。如果要修改类属性,必须通过类名去修改,比如Student.school_name = "新学校"。
另外,如果类属性是可变对象(比如列表、字典),通过实例修改这个可变对象的内容时,会同步影响所有实例,因为所有实例访问到的是同一个可变对象。示例如下:
class Student:
# 类属性是可变列表
scores = []
def add_score(self, score):
self.scores.append(score)
stu1 = Student()
stu2 = Student()
stu1.add_score(90)
print(stu2.scores) # 输出:[90],类属性被修改,所有实例都能看到
如果希望每个实例有独立的列表属性,应该在__init__方法中定义实例属性,而不是使用类属性。