Python面向对象:实例属性与类属性的区别澄清
2026/8/9 16:58:18 网站建设 项目流程

Python面向对象:实例属性与类属性的区别澄清

一、开篇:同一类名下,两种属性

在Python类中,有两种属性:实例属性(每个对象各自拥有)和类属性(所有对象共享)。混淆这两者是最常见的OOP陷阱之一。

⌨️ 先看直观对比:

classDog:species="犬科动物"# 类属性——所有狗共享count=0# 类属性——统计狗的数量def__init__(self,name,age):self.name=name# 实例属性——每只狗独有self.age=age# 实例属性——每只狗独有Dog.count+=1# 修改类属性dog1=Dog("旺财",3)dog2=Dog("小黑",1)print(dog1.name,dog1.age)# 旺财 3 —— 实例属性print(dog2.name,dog2.age)# 小黑 1 —— 各不相同print(dog1.species,dog2.species)# 犬科动物 犬科动物 —— 共享print(Dog.count)# 2 —— 类属性

二、访问机制和优先级

2.1 实例属性覆盖类属性

classPerson:# 类属性(默认值)species="人类"planet="地球"def__init__(self,name):self.name=name# 实例属性p=Person("张三")# 访问species——实例没有,查找类属性print(p.species)# "人类" —— 从类属性来的print(Person.species)# "人类"# 给实例添加同名属性——会覆盖类属性!p.species="火星人"# ⚠️ 这是给p创建了实例属性!print(p.species)# "火星人" —— 实例属性print(Person.species)# "人类" —— 类属性没有变化!# 删除实例属性——类属性重新可见delp.speciesprint(p.species)# "人类" —— 又回到类属性了

2.2 通过实例修改类属性的陷阱

classCounter:count=0# 类属性c1=Counter()c2=Counter()# ✅ 通过类修改——影响所有实例Counter.count=100print(c1.count,c2.count)# 100 100# ⚠️ 通过实例"修改"——实际是创建了实例属性!c1.count=999# 不是修改类属性!是在c1上创建了实例属性!print(c1.count)# 999 —— 实例属性print(c2.count)# 100 —— 类属性(没有被修改)print(Counter.count)# 100 —— 类属性不变!# 💡 这就是最经典的陷阱!# c1.count = 999 看起来像是在修改类属性# 实际上是在c1上创建了一个同名的实例属性# 类属性 Counter.count 完全没有变化# 验证:c1有实例属性,c2没有print("c1.__dict__:",c1.__dict__)# {'count': 999}print("c2.__dict__:",c2.__dict__)# {}

2.3 可变类属性的共享陷阱

# ⚠️ 类属性如果是可变对象——所有实例真的共享它!classClassroom:students=[]# 可变类属性——所有实例共享同一个列表!def__init__(self,name):self.name=namedefadd_student(self,student_name):self.students.append(student_name)# 修改共享的列表room1=Classroom("一班")room2=Classroom("二班")room1.add_student("张三")room2.add_student("李四")# 两个教室共享了学生列表!print(room1.students)# ['张三', '李四']print(room2.students)# ['张三', '李四'] —— 一样的!# ✅ 正确的做法:在__init__中初始化classClassroomFixed:def__init__(self,name):self.name=name self.students=[]# 每个实例有自己的列表!defadd_student(self,student_name):self.students.append(student_name)

三、类属性的典型应用

# 场景一:类常量classMathConstants:PI=3.141592653589793E=2.718281828459045GOLDEN_RATIO=1.618033988749895print(MathConstants.PI)# 不需要创建实例# 场景二:实例计数器classUser:_total_users=0# 类属性——追踪创建了多少用户def__init__(self,name):self.name=name User._total_users+=1self._id=User._total_users# 当前用户的ID@classmethoddefget_total(cls):returncls._total_users u1=User("张三")u2=User("李四")u3=User("王五")print(f"共创建{User.get_total()}个用户")# 3# 场景三:默认配置classDatabaseConfig:# 类属性作为默认配置host="localhost"port=5432database="mydb"timeout=30@classmethoddefoverride(cls,**kwargs):"""批量覆盖默认配置"""forkey,valueinkwargs.items():setattr(cls,key,value)DatabaseConfig.override(host="192.168.1.100",port=5433)print(DatabaseConfig.host)# 192.168.1.100

四、总结

区分类属性和实例属性是Python OOP的基本功。记住那条简单的规则:__init__中通过self.xxx定义的是实例属性,在类体中直接定义的是类属性。

💡核心要点:

实例属性类属性
定义位置__init__self.xxx类体中直接定义
存储位置实例.__dict__类.__dict__
作用范围每个实例独有所有实例共享
修改方式实例.xxx = value类名.xxx = value

⚠️关键陷阱:实例.类属性名 = 值会创建实例属性,遮蔽类属性——而不会修改类属性!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询