学到这里,很多人会开始有一种感觉:
类、对象、实例属性、实例方法、__init__、self 这一套,刚刚有点顺了。结果又冒出来三个新词:
类属性 类方法 静态方法
一看名字就很像,一不小心就会混成一团。
这是非常正常的。因为这三样东西确实都和“类”有关,但它们解决的问题并不一样。很多新手卡在这里,不是因为太难,而是因为没先把“它们各自服务谁”这件事想清楚。
这一章,我们就专门把这三个概念讲透,而且不绕弯。
你只要先记住一句总纲:
实例的东西,主要服务某个具体对象。类的东西,主要服务整个类。静态方法,更像是放在类名下面的普通工具函数。
这一句如果先立住,后面就会轻松很多。
一、先从最熟悉的实例属性和实例方法出发
在讲新东西之前,我们先把熟悉的部分拿出来对照。
比如:
classStudent:def__init__(self, name, score): self.name = name self.score = scoredefintroduce(self): print(f'我叫{self.name},成绩是{self.score}')
这里的 self.name 和 self.score,是实例属性。introduce() 是实例方法。
它们的特点非常明确:
属于某个具体对象 不同对象可以有不同值 方法里通常会用到 self
比如:
stu1 = Student('张三', 95)stu2 = Student('李四', 88)stu1.introduce()stu2.introduce()
这里两个对象的数据明显不同。
也就是说,实例这一套,重点在“对象自己”。
那接下来我们就要问:
如果有一些数据,不应该每个对象单独一份,而是整类对象共享一份,该怎么办? 如果有一些方法,不是服务某个具体对象,而是服务整个类,又该怎么办? 如果有一些函数,只是逻辑上和这个类相关,但其实既不依赖对象,也不依赖类本身,又该怎么办?
这三个问题,正好对应今天这章的三个主角。
二、什么是类属性
类属性,先给最直白的定义:
类属性是属于类本身的属性,而不是某个具体对象独有的属性。
换句话说,它是这一类对象共享的数据。
比如一个学生类里,所有学生都属于同一所学校。 那么“学校名称”这个数据,就不适合放成每个对象各自一份。因为张三和李四都在这个学校,没必要一人存一遍。
这时候就很适合用类属性。
看代码:
classStudent: school = '第一中学'def__init__(self, name, score): self.name = name self.score = score
这里的:
school = '第一中学'
就叫类属性。
注意它写的位置。 它不是在 __init__ 里,也不是 self.school。 它是直接写在类体里面,和方法同级。
这说明它属于整个类,而不是某一个对象。
三、类属性和实例属性最大的区别是什么
最核心的区别只有一句话:
实例属性属于对象自己,类属性属于整个类。
还是拿学生类举例。
如果是:
self.name = nameself.score = score
那这是每个学生对象自己的数据。张三和李四当然不一样。
如果是:
school = '第一中学'
那这是所有学生共同拥有的设定。
你可以这么理解:
姓名、成绩,显然是“一个学生一个样”,所以是实例属性。 学校名称、学生总数、统一学期编号这种,更像“这一类对象共享的信息”,所以适合类属性。
这个分类标准,比死记定义有用得多。
四、类属性怎么访问
类属性有两种常见访问方式。
第一种,用类名访问:
print(Student.school)
第二种,用对象访问:
stu1 = Student('张三', 95)print(stu1.school)
这两种通常都能拿到值。
例如:
classStudent: school = '第一中学'def__init__(self, name, score): self.name = name self.score = scorestu1 = Student('张三', 95)stu2 = Student('李四', 88)print(Student.school)print(stu1.school)print(stu2.school)
输出结果:
第一中学第一中学第一中学
但从习惯上说,类属性更推荐用类名来访问。因为它本来就是类级别的数据,用类名访问更清楚,也更不容易混淆。
五、什么时候该用类属性
这是比定义更重要的问题。
你可以用一个判断标准:
如果一份数据应该被所有对象共享,而且逻辑上属于整个类,就可以考虑用类属性。
比如:
学生类里的学校名称 员工类里的公司名称 商品类里的税率 账户类里的统一利率 游戏角色类里的默认最大等级
这些东西,通常不是某个对象私人独有,而是整个类别共用的规则或背景信息。
再比如我们可以写:
classEmployee: company = '未来科技有限公司'def__init__(self, name, salary): self.name = name self.salary = salary
这里所有员工都属于同一家公司,所以 company 很适合做类属性。
六、类属性最典型的实战用途之一:计数
类属性有一个非常经典的用法,就是记录“总共有多少个对象被创建了”。
例如:
classStudent: count = 0def__init__(self, name): self.name = name Student.count += 1
创建对象:
stu1 = Student('张三')stu2 = Student('李四')stu3 = Student('王五')print(Student.count)
输出结果:
3
这里的 count 就很适合用类属性。
因为它不是某个学生自己的数据。 它表示的是整个 Student 这一类对象一共创建了多少个。
这类场景,你后面会经常见到。
七、什么是类方法
先给定义:
类方法是绑定给类的方法,它的第一个参数通常是 cls,表示当前类本身。
注意,它和实例方法很像,但服务对象不同。
实例方法的第一个参数通常是 self,表示当前对象。 类方法的第一个参数通常是 cls,表示当前类。
它的典型写法要配合一个装饰器:
@classmethod
例如:
classStudent: school = '第一中学'def__init__(self, name): self.name = name @classmethoddefshow_school(cls): print(f'学校名称是:{cls.school}')
调用:
Student.show_school()
输出结果:
学校名称是:第一中学
这里的 show_school() 就是一个类方法。
八、类方法为什么要用 cls
你现在可以把 cls 粗略理解成:
当前这个类自己。
这和前一章里的 self 是同一个思路。
self 表示当前对象自己。cls 表示当前类自己。
所以这句:
print(cls.school)
意思就是:
读取当前类自己的 school 类属性。
你会发现,这和实例方法的逻辑其实很统一。只是服务层级不同。
九、类方法最适合做什么
类方法最适合做两类事。
第一类,操作类属性。 第二类,写一些“逻辑上更适合由类来完成”的功能。
最常见的就是第一类。
比如前面那个计数例子,我们就可以再加一个类方法来展示当前总人数:
classStudent: count = 0def__init__(self, name): self.name = name Student.count += 1 @classmethoddefshow_count(cls): print(f'当前一共创建了 {cls.count} 个学生对象')
调用:
stu1 = Student('张三')stu2 = Student('李四')Student.show_count()
输出结果:
当前一共创建了 2 个学生对象
这时候你会明显感觉到,show_count() 更适合归类来管,而不是归某个学生对象来管。
因为“总人数”本来就不是某个对象个人的事,而是整个类层面的信息。
十、类方法和实例方法最根本的区别
一句话概括:
实例方法主要操作对象自己的数据,类方法主要操作类自己的数据。
比如:
defintroduce(self): print(self.name)
这里关心的是对象的姓名,是实例方法。
而:
@classmethoddefshow_school(cls): print(cls.school)
这里关心的是学校名称,是类属性,所以更适合类方法。
你可以这样粗略分工:
对象的个体行为,用实例方法。 整类共享信息的处理,用类方法。
这样分,基本不会太乱。
十一、什么是静态方法
现在到了第三个最容易让人懵的东西:静态方法。
先给一个非常接地气的定义:
静态方法是放在类里面的普通函数。它既不依赖对象,也不依赖类本身。
它的写法要配合:
@staticmethod
例如:
classMathTool: @staticmethoddefadd(a, b):return a + b
调用:
print(MathTool.add(3, 5))
输出结果:
8
这里你会发现一个明显特点:
这个方法里既没有 self,也没有 cls。 因为它根本不需要知道是谁在调用,也不需要访问类属性或实例属性。
它只是一个逻辑上“和这个类放在一起比较合适”的工具函数。
十二、静态方法为什么还要放进类里
这正是很多人疑惑的地方。
既然它这么像普通函数,那为什么不直接写成类外面的函数?
答案是:
因为逻辑上它和这个类有关,把它放进类里更利于组织代码。
比如一个用户类,可能会有一个检查手机号是否合法的工具方法。这个方法不需要访问某个具体用户对象,也不需要依赖整个类属性,但它和“用户”这个主题明显相关。
例如:
classUser: @staticmethoddefis_valid_phone(phone):return len(phone) == 11and phone.isdigit()
调用:
print(User.is_valid_phone('13800138000'))print(User.is_valid_phone('abc'))
输出结果:
TrueFalse
你看,这个逻辑放在 User 类下面就很顺。因为它本来就是“和用户数据校验有关的一个工具”。
所以静态方法的本质,不是能力更强,而是分类更清晰。
十三、实例方法、类方法、静态方法,先从“第一个参数”来区分
这是一个很实用的入门区分法。
实例方法:
def 方法名(self, ...)
类方法:
@classmethoddef 方法名(cls, ...)
静态方法:
@staticmethoddef 方法名(...)
你会发现:
实例方法有 self类方法有 cls静态方法啥都没有
为什么?
因为它们依赖的信息层级不同。
实例方法要操作对象,所以要有 self。 类方法要操作类,所以要有 cls。 静态方法两边都不依赖,所以都不要。
这个角度一旦想通,三者的边界就会清晰很多。
十四、再从“它们能访问什么”来区分
这是第二个很实用的判断角度。
实例方法,通常可以访问对象属性,也能间接访问类属性。 类方法,通常可以访问类属性,但不能直接访问某个对象独有的数据。 静态方法,默认不自动拿到对象和类,所以它更像一个独立工具。
比如:
classStudent: school = '第一中学'def__init__(self, name): self.name = namedefmethod1(self): print(self.name) print(Student.school) @classmethoddefmethod2(cls): print(cls.school) @staticmethoddefmethod3(): print('这是一个静态方法')
这里:
method1 能用对象姓名,也能用学校method2 更适合用学校这种类级别信息method3 只是执行自己那点逻辑
你不一定要立刻把所有边角都抠透,但这个大框架一定要立住。
十五、三者最直白的生活化类比
你可以把一个班级想成一个类。
实例方法像什么?
像“某个学生介绍自己”。 这显然得看具体是哪位学生,所以要用 self。
类方法像什么?
像“教务处统计这个班一共有多少人”。 这个事情属于整个班级层面,不是某个学生个人,所以用 cls。
静态方法像什么?
像“判断一个学号格式是否正确的规则”。 这个规则和班级主题有关,但既不依赖某个具体学生,也不依赖班级当前状态,所以它更像一个独立工具。
这样一类比,很多人就不再混了。
十六、三者怎么调用
这点也很重要。
实例方法通常通过对象调用:
stu1.introduce()
类方法通常通过类名调用更自然:
Student.show_count()
静态方法也常通过类名调用:
User.is_valid_phone('13800138000')
当然,Python 里有些情况下对象也能调类方法和静态方法,但在入门阶段,你最好先养成更清晰的习惯:
实例方法,优先对象调。 类方法,优先类名调。 静态方法,优先类名调。
这样代码表达会更稳。
十七、一个完整案例,把三者放一起看
下面这段代码建议你认真看一遍:
classStudent: school = '第一中学' count = 0def__init__(self, name, score): self.name = name self.score = score Student.count += 1defintroduce(self): print(f'我叫{self.name},来自{Student.school},成绩是{self.score}') @classmethoddefshow_count(cls): print(f'当前共有 {cls.count} 个学生对象') @staticmethoddefis_valid_score(score):return0 <= score <= 100
调用:
stu1 = Student('张三', 95)stu2 = Student('李四', 88)stu1.introduce()stu2.introduce()Student.show_count()print(Student.is_valid_score(90))print(Student.is_valid_score(120))
输出结果:
我叫张三,来自第一中学,成绩是95我叫李四,来自第一中学,成绩是88当前共有 2 个学生对象TrueFalse
这段代码非常有代表性。
school 和 count 是类属性introduce() 是实例方法show_count() 是类方法is_valid_score() 是静态方法
如果这一段你能彻底看顺,这章主干就基本拿下了。
十八、什么时候该用类属性,什么时候别乱用
再强调一次,类属性很适合共享数据。 但不是所有数据都该写成类属性。
比如学生姓名、年龄、成绩,这些显然是每个对象都不同的,就应该是实例属性。 如果你把这些也写成类属性,所有对象看起来就容易共用同一份数据,那就会很奇怪。
所以一个很重要的判断是:
这份数据是“每个对象各不相同”,还是“整类对象共用一份”。
前者一般是实例属性。 后者才更适合类属性。
十九、什么时候该用类方法,什么时候别硬套
类方法也不是越多越好。
如果你的方法逻辑明显围绕某个对象在展开,比如展示姓名、修改余额、更新电量、判断某个学生是否及格,这些都更适合实例方法。
如果你的逻辑明显和整个类有关,比如统计总数、读取统一配置、修改共享规则,那才更适合类方法。
也就是说,不是加了 @classmethod 就显得高级。 关键还是看它到底服务谁。
二十、什么时候该用静态方法
静态方法最容易被误用。
你可以先用一个很稳的判断法:
如果一个函数逻辑上和这个类有关,但既不需要访问实例属性,也不需要访问类属性,那就可以考虑静态方法。
比如:
判断分数是否合法 判断手机号格式是否正确 把某个字符串做标准化处理 计算两个值的简单关系
这些都可能适合静态方法。
但如果这个函数和类压根没什么关系,那其实完全可以写成类外的普通函数,也没必要为了“显得懂面向对象”硬塞进类里。
二十一、很多新手为什么会混
因为表面上看,它们都写在类里。 可真正的区别不在“写在哪”,而在“依赖谁”。
实例方法依赖对象。 类方法依赖类。 静态方法两边都不依赖。
所以你以后别从语法表面去背,而是先问自己:
这个方法是服务某个对象的吗 还是服务整个类的 还是只是一个放在这里比较顺手的工具函数
一旦先想清这个,语法只是顺手一写。
二十二、给你一个最实用的判断口诀
你可以先用下面这个入门口诀,特别够用:
要用对象数据,用实例方法。 要用类共享数据,用类方法。 谁都不用,只是工具,用静态方法。
再补一句:
每个对象不同的数据,用实例属性。 整类对象共享的数据,用类属性。
这个口诀不是百分之百覆盖所有细节,但对你当前阶段已经非常够用了。
二十三、本章小练习
你可以自己做一个非常适合巩固的练习。
定义一个 Book 类,要求如下:
有一个类属性 category,值是 编程类图书有一个类属性 count,用于统计创建了多少本书__init__ 接收 title 和 price,并更新 count写一个实例方法 show_info(),输出书名、价格、类别 写一个类方法 show_count(),输出当前一共创建了多少本书 写一个静态方法 is_valid_price(price),判断价格是否大于 0
参考代码如下:
classBook: category = '编程类图书' count = 0def__init__(self, title, price): self.title = title self.price = price Book.count += 1defshow_info(self): print(f'书名:{self.title},价格:{self.price}元,类别:{Book.category}') @classmethoddefshow_count(cls): print(f'当前一共创建了 {cls.count} 本书') @staticmethoddefis_valid_price(price):return price > 0
你把它补完整,再自己创建两个对象试一遍,这章会非常扎实。
二十四、本章总结
这一章,你要真正分清三件事。
类属性,是属于整个类的共享数据。 实例属性,是属于每个对象自己的独立数据。 类方法,用 @classmethod 定义,第一个参数通常是 cls,主要服务类本身。 实例方法,用 self,主要服务具体对象。 静态方法,用 @staticmethod 定义,既不依赖对象,也不依赖类,更像放在类里的工具函数。 判断它们的关键,不是背名字,而是先看它们到底依赖谁、服务谁。
到这里,面向对象的基础结构已经越来越完整了。 下一章我们继续往前走,进入一个非常重要、也非常有现实感的话题:077|封装:为什么说“隐藏细节”很重要。