目录
一、两种属性的区别
二、属性读取演示
完整演示代码
三、类属性全局修改
四、实例赋值的坑
五、属性查找规则
六、完整流程示例
七、del 删除属性
八、实战:统计实例数量
九、动态添加类属性
十、两类属性对比
十一、易错清单
十二、本章小结
上一章我们学会了基础的类、实例、__init__构造方法与实例属性,我们知道self.xxx是每一个对象独有的数据:每只小猫有自己的名字、毛色,每台手机有专属品牌、售价,互相不会干扰。但现实里还有一类数据,属于同一类事物全体共用,不需要每个实例单独存一份。比如所有手机都属于“智能手机”这个大类,这个分类标签不用苹果、小米各自存一遍,放在模板里统一管理就足够。
今天我们就来拆解两种属性的核心区别:实例属性(对象独有)和类属性(全类共享),理清读取、修改的全部规则,避开极易踩坑的同名赋值陷阱。
一、两种属性的区别
我们以手机产品模板来举例,直观看懂两种数据的定位:
class Phone: # 类属性:全品类手机共用分类标签 category = "智能手机" def __init__(self, brand, price): # 实例属性:单台手机独有的信息 self.brand = brand self.price = price1. 类属性 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) # 39992. 删除类全局属性
类属性属于整个类共享,用del 类名.属性名删除后,所有实例再读取该属性都会报错:
class Phone: category = "智能手机" p1 = Phone("苹果") p2 = Phone("小米") # 删除类属性 category del Phone.category # 所有实例再读取都会报错 print(p1.category) # AttributeError print(p2.category) # AttributeError3. 对比: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 代码