目录
上一章我们学会了基础的类、实例、__init__构造方法与实例属性,我们知道self.xxx是每一个对象独有的数据:每只小猫有自己的名字、毛色,每台手机有专属品牌、售价,互相不会干扰。但现实里还有一类数据,属于同一类事物全体共用,不需要每个实例单独存一份。比如所有手机都属于“智能手机”这个大类,这个分类标签不用苹果、小米各自存一遍,放在模板里统一管理就足够。
今天我们就来拆解两种属性的核心区别:实例属性(对象独有)和类属性(全类共享),理清读取、修改的全部规则,避开极易踩坑的同名赋值陷阱。
一、两种属性的区别
我们以手机产品模板来举例,直观看懂两种数据的定位:
class Phone:
# 类属性:全品类手机共用分类标签
category = "智能手机"
def __init__(self, brand, price):
# 实例属性:单台手机独有的信息
self.brand = brand
self.price = price
1. 类属性 category
相当于产品大类统一标识,所有手机共用这一条内容,只保存一份。如果修改这个分类名称,所有手机读取到的标签都会同步更新。
2. 实例属性 brand、price
是单台设备专属信息,华为、小米各自的品牌、价格互不干扰,修改其中一台不会影响其他设备。
简单总结:
- 实例属性 = 每个对象私有信息,彼此隔离;
- 类属性 = 全类共享信息,一处修改,全部对象同步生效。
二、属性读取演示
完整演示代码
class Phone(object):
# 定义类属性,放在所有函数外部
category = "智能手机"
def __init__(self, brand, price):
# self开头为实例属性,每个实例独立
self.brand = brand
self.price = price
新建两台手机实例
p1 = Phone("苹果", 5999)
p2 = Phone("小米", 3999)
读取实例专属数据,两台结果不一样
print(p1.brand)
print(p2.brand)
print(p1.price)
print(p2.price)
读取共享类属性,两种写法都可以
写法1:直接使用类名读取,规范推荐
print(Phone.category)
写法2:通过实例读取,结果完全相同
print(p1.category)
print(p2.category)
运行输出结果:
苹果
小米
5999
3999
智能手机
智能手机
智能手机
能看出,不管通过类还是任意实例,拿到的类属性数值完全一致。
三、类属性全局修改
如果使用「类名.属性名」修改类属性,所有创建好的实例再读取时,都会拿到更新后的内容。下面给出完整可运行的代码,包含完整的 Phone 类定义:
class Phone:
# 类属性:全品类手机共用分类标签
category = "智能手机"
def __init__(self, brand, price):
# 实例属性:单台手机独有的信息
self.brand = brand
self.price = price
p1 = Phone("苹果", 5999)
p2 = Phone("小米", 3999)
通过类统一修改共享分类
Phone.category = "5G智能手机"
两台设备读取到的内容同步变化
print(p1.category)
print(p2.category)
print(Phone.category)
输出内容统一变为5G智能手机,实现全局统一修改。
四、实例赋值的坑
很多初学者会直接用实例修改类属性名称,这种操作不会改动全局共享数据,只会给当前实例新增一份同名私有数据,造成数据隔离。沿用第二节的 Phone 类,只贴改动片段:
p1 = Phone("苹果", 5999)
p2 = Phone("小米", 3999)
# 错误操作:通过实例给 category 赋值
p1.category = "老年机"
# p1 只会读取自己新增的私有数据
print(p1.category)
# 类和其他实例不受任何影响
print(Phone.category)
print(p2.category)
运行输出:
老年机
智能手机
智能手机
底层逻辑:实例读取数据时,会优先查找自身携带的属性,只有自身不存在对应字段时,才会读取类里的共享数据。一旦给实例新增同名属性,全局共享内容就不再对它生效。
五、属性查找规则
为什么实例赋值会「隔离」出私有数据?答案藏在 Python 的属性查找顺序里。这是理解两种属性差异的核心难点,务必掌握。
属性查找的完整底层规则:Python实例读取属性时,会按照固定顺序逐层查找:
- 先在当前实例自身查找该属性,找到就直接使用;
- 实例自身不存在该属性,再去所属类中查找类属性;
- 类中也没有,就抛出
AttributeError报错。
配套演示报错代码:
class Phone:
category = "手机"
p = Phone("小米")
print(p.price) # 实例、类都无 price,直接报错
运行这段代码会抛出AttributeError: 'Phone' object has no attribute 'price',因为实例p自身没有price,类Phone里也没有price,两层都找不到,最终只能报错。
六、完整流程示例
配套简短代码,完整走完定义、读取、修改、隔离全过程。沿用第二节的 Phone 类,只贴改动片段:
p1 = Phone("apple")
p2 = Phone("xiaomi")
# 初始共享标签
print(Phone.category)
print(p1.category)
print(p2.category)
# 类统一修改标签
Phone.category = "手机"
print(p1.category)
print(Phone.category)
print(p2.category)
# 实例创建独立同名属性
p1.category = "5G手机"
print(p1.category)
print(Phone.category)
print(p2.category)
七、del 删除属性
除了读取和修改,属性还可以用 del 关键字删除。删除实例独有属性和删除类全局属性,效果完全不同,下面分别演示
1. 删除实例独有属性
实例属性是每个对象单独持有的数据,用 del 实例.属性名 可以把它从当前实例上移除,其他实例不受影响:
class Phone:
def __init__(self, brand, price):
self.brand = brand
self.price = price
p1 = Phone("苹果", 5999)
p2 = Phone("小米", 3999)
# 删除 p1 的实例属性 price
del p1.price
# p1 再读取 price 会报错
print(p1.price) # AttributeError
# p2 的 price 不受影响
print(p2.price) # 3999
2. 删除类全局属性
类属性属于整个类共享,用 del 类名.属性名 删除后,所有实例再读取该属性都会报错:
class Phone:
category = "智能手机"
p1 = Phone("苹果")
p2 = Phone("小米")
# 删除类属性 category
del Phone.category
# 所有实例再读取都会报错
print(p1.category) # AttributeError
print(p2.category) # AttributeError
3. 对比:del 实例同名属性后,会重新读取类属性
结合前面学过的查找规则,如果实例自身有同名属性,删除它之后,实例会重新回到类里读取共享的类属性:
class Phone:
category = "智能手机"
p1 = Phone("苹果")
# 给实例新增同名属性,覆盖类属性
p1.category = "老年机"
print(p1.category) # 老年机(读取实例自身)
# 删除实例自带的 category
del p1.category
# 实例自身没有了,重新读取类属性
print(p1.category) # 智能手机
原因解释:del p1.category 只删除实例自身新增的同名属性,类里共享的 category 仍然存在。删除后实例再读取时,自身找不到该属性,就会按查找顺序回到类中读取,所以输出恢复为 智能手机。
八、实战:统计实例数量
类属性最常用的场景是全局统计,记录一共创建了多少个实例,不需要为每一个对象单独记录数字。沿用第二节的 Phone 类,只贴改动片段:
class Phone:
# 类属性:全局计数器,所有实例共用
count = 0
category = "智能手机"
def __init__(self, brand):
# 实例属性:单台手机独有的信息
self.brand = brand
# 每创建一台设备,总数 +1
Phone.count += 1
连续创建三台设备
p1 = Phone("华为")
p2 = Phone("vivo")
p3 = Phone("oppo")
读取全局统计数量
print("已生产设备总数量:", Phone.count)
print("设备统一分类:", Phone.category)
运行结果:
已生产设备总数量: 3
设备统一分类:智能手机
反面错误案例:不要用 self.count += 1 做全局计数
很多新手会写成 self.count += 1,以为这样就能实现全局计数,其实这是错误写法,只会给当前实例新增一份独立的 count,全局共享的 count 永远保持初始值:
class Phone:
count = 0
def __init__(self, brand):
self.brand = brand
self.count += 1 # 错误写法!只会给实例新增 count,全局 count 永远为 0
p1 = Phone("华为")
print(Phone.count) # 输出 0,计数失效
原因解释:self.count += 1 等价于先读取再赋值,由于实例自身没有 count,读取时拿到的是类里的共享值 0,但赋值时却是在当前实例上新建了一个同名属性 count,并不会修改类共享的 count。所以全局计数必须写成 Phone.count += 1,直接通过类名修改类属性,才能让所有实例同步看到更新。
九、动态添加类属性
前面看到的类属性都是写在 class 代码块内部,其实类属性并不强制要求定义在类里。代码任意位置,只要通过「类名.属性名」赋值,就能给类动态新增一个类属性,所有实例同样可以读取到:
class Phone:
pass
类外部动态添加类属性
Phone.count = 0
p1 = Phone()
p2 = Phone()
所有实例都能读取到动态新增的类属性
print(Phone.count) # 0
print(p1.count) # 0
print(p2.count) # 0
运行结果:
0
0
0
原因解释:类属性本质上就是挂在类对象上的一个名字,只要通过 类名.属性名 = 值 赋值,无论写在类定义内部还是外部,效果完全一样。这也说明类属性不局限于写在类定义里,代码任意位置通过类名都能新增,进一步拓展了对类属性的认知。
业务场景举例:程序运行中动态新增分类标签。比如电商后台在运营过程中新增了一个「限时秒杀」分类,不需要修改类定义代码,直接通过 Goods.category = "限时秒杀" 动态挂到类上,所有已创建的商品实例立刻都能读取到这个新标签,无需逐个对象赋值。
十、两类属性对比
到这里,实例属性和类属性的核心差异已经全部讲完。下面用一张对比表,把两者的定义位置、存储方式、读取方式、修改影响和删除效果一次性梳理清楚,方便复习和查阅。
| 对比维度 | 实例属性 | 类属性 |
|---|---|---|
| 定义位置 | 在 __init__ 方法内,通过 self.属性名 定义 | 在类代码块内、所有方法外部,直接 属性名 = 值 定义 |
| 存储方式 | 每个实例单独保存一份,互不干扰 | 整个类只保存一份,所有实例共享 |
| 读取方式 | 实例.属性名 | 类名.属性名 或 实例.属性名 均可 |
| 通过类修改 | 不适用(类无法直接修改实例属性) | 类名.属性名 = 新值,所有实例同步更新 |
| 通过实例赋值 | 正常修改当前实例自己的数据 | 不会修改类属性,只会给当前实例新增同名私有属性 |
| 删除方式 | del 实例.属性名,只影响当前实例 | del 类名.属性名,所有实例再读取都会报错 |
| 典型应用 | 每台手机的品牌、价格等专属信息 | 全品类统一分类标签、全局计数器等共享数据 |
一句话记忆:实例属性管「个体差异」,类属性管「群体共性」。写代码时先想清楚这份数据是每个对象各自持有,还是全体对象共用,再决定用哪种属性,就能避免绝大多数混淆。
十一、易错清单
本章涉及多个容易踩坑的细节,下面把高频易错点集中列出来,配合正确写法一起对照
易错点 1:用实例赋值想修改类属性
错误写法:p1.category = "老年机",以为能改全局分类。
正确认知:这只会给 p1 新增一份同名私有属性,类和其他实例完全不受影响。要修改类属性,必须用 Phone.category = "老年机"。
易错点 2:用 self.count += 1 做全局计数
错误写法:在 __init__ 里写 self.count += 1,以为能统计实例总数。
正确写法:必须写成 Phone.count += 1,直接通过类名修改类属性,才能让所有实例共享同一个计数器。
易错点 3:忘记属性查找顺序
实例读取属性时,永远先找自身,再找类。一旦实例有了同名属性,类里的共享值就被「屏蔽」了。删除实例同名属性后,又会重新读取到类属性。
易错点 4:删除类属性导致全局报错
del Phone.category 会删除整个类共享的属性,之后所有实例再读取 category 都会抛出 AttributeError。删除类属性前务必确认没有其他地方还在使用它。
易错点 5:混淆「读取」和「赋值」的路径
读取时 p1.category 和 Phone.category 结果一样,但赋值时两者效果完全不同:前者新增实例属性,后者修改类属性。读取和赋值要分开记忆,不能混为一谈。
十二、本章小结
本章围绕实例属性和类属性,系统梳理了从定义、读取、修改到删除的完整规则,核心要点可以概括为以下几条:
- 实例属性是每个对象独有的数据,通过
self.属性名定义,彼此隔离,互不影响。 - 类属性是全类共享的数据,定义在类代码块内、方法外部,一处修改,所有实例同步生效。
- 读取规则:实例读取属性时,先找自身,再找类,两层都没有就抛出
AttributeError。 - 修改规则:通过类名修改类属性才能全局生效;通过实例赋值只会新增同名私有属性,不会改动类属性。
- 删除规则:
del 实例.属性名只影响当前实例;del 类名.属性名影响所有实例。 - 实用场景:类属性非常适合做全局计数器、统一分类标签等共享数据;实例属性适合保存每个对象的专属信息。
掌握好「实例管个体、类管群体」这条主线,再配合属性查找顺序,就能在实战中准确判断该用哪种属性,写出更清晰、更不易出错的 Python 代码

367

被折叠的 条评论
为什么被折叠?



