【Python 进阶】面向对象编程,一定要知道的类变量、类方法、静态方法
让我们首先从一个简单的例子开始。想象我们现在需要开发一个学生管理系统,那么自然会有一个这样的 Student 类,输入姓名和性别,来实例化一个学生。
1
2
3
4
5
6
7
class Student:
def __init__(self, name, sex):
self.name = name
self.sex = sex
stu = Student('Qiqi', 'Female')
实际的案例当然没有这么简单,我进行了简化,为了更好 focus 在本次的主题上。
通过这个类,我们可以轻松定义一名学生,包含这个学生的名字、性别或者其他和这名学生相关的信息。
但如果我想要知道和整个 Student 类有关的信息,比如一共有多少学生。实例方法或者实例变量无法帮助我们知道,因为它们仅与单个实例有关,也就是只和单个学生有关。
于是,这就来到了我们今天的主题,类变量、类方法、和静态方法。
基本使用
类变量
现在,我在 Student 类下面新增一个变量 student_num
,用于记录学生人数。
1
2
class Student:
student_num = 0
可以看到 student_num
不同于一般的实例变量,它不需要在 构造方法中定义,而是直接在类下面定义。
同时,我需要在构造方法中改变 student_num
,使得每定义一个学生,student_num
就往上加一。
1
2
3
4
def __init__(self, name, sex):
self.name = name
self.sex = sex
student_num += 1
运行一下,噢报错了!那是因为我们需要通过类来访问 student_num
。
1
2
3
4
def __init__(self, name, sex):
self.name = name
self.sex = sex
Student.student_num += 1
运行一下看看!
1
2
3
4
5
print(f'Student.student_num:{Student.student_num}')
'''
Student.student_num:1
'''
符合我们的预期,学生人数变成了 1!
访问类变量,通过类还是实例?
刚刚提到类变量需要通过类访问,实际上,通过实例访问也不会报错。
1
2
3
4
def __init__(self, name, sex):
self.name = name
self.sex = sex
self.student_num += 1
但让我们打印一下……
1
2
3
4
5
6
7
print(f'Student.student_num:{Student.student_num}')
print(f'stu.student_num:{stu.student_num}')
'''
Student.student_num:0
stu.student_num:1
'''
两者的数量不一样!这里确实让人有点困惑。
其中的原因是,在执行 self.student_num += 1
时,Python 需要先取值,再写入。
实例中不存在 student_num
这一属性,因此取值时,从类里面获取了 student_num
的值 0;而写入时,给实例增加了这一属性写入。
因此,为了不造成混乱,我建议通过类访问。
类方法
类方法需要使用装饰器 @classmethod
进行修饰。
1
@classmethod
关于装饰器使用我会在之后专门讲解,现在让我们 focus 在类方法的使用上。
我要定义一个类方法用于一次添加多个学生,给 student_num
加上一个数。
1
2
3
@classmethod
def add_students(cls, add_num):
cls.student_num += add_num
注意,类方法的第一个参数需要是类本身,本例中我们使用 cls 表示 class,这也是被广泛使用的表达方式。通过 cls 作为参数,可以访问到类变量类方法等类里面的东西。
使用类方法替代构造方法
使用类方法代替构造方法是一种常见的做法,尤其在输入的形式比较多的时候。以我们目前的代码,实例化一名学生的时候需要分别输入名字和性别,但有时候输入可能是一段字符串或者一个列表。
1
stu1 = Student('Qiqi Female')
这样就不满足构造方法的要求了,但在一个庞大的系统中,输入的形式五花八门是很常见的。一个常见的解决办法是定义几个类方法解析输入,再执行构造函数。
现在,我定义一个解析字符串的类方法。
1
2
3
4
5
6
7
8
9
10
11
12
@classmethod
def from_string(cls, info):
name, sex = info.split(' ')
return cls(name, sex)
stu1 = Student.from_string('Qiqi Male')
print(f'stu.name: {stu.name}\nstu.sex: {stu.sex}')
'''
stu.name:Qiqi
stu.sex:Male
'''
在类方法 from_string
中解析输入,再调用构造函数。这个思路同样可以用来解析列表,字典等等。
在官方库中,这种做法被广泛使用,让我们看看 datetime.date
是怎么做的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@classmethod
def fromtimestamp(cls, t):
"Construct a date from a POSIX timestamp (like time.time())."
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
return cls(y, m, d)
@classmethod
def fromordinal(cls, n):
"""Construct a date from a proleptic Gregorian ordinal.
January 1 of year 1 is day 1. Only the year, month and day are
non-zero in the result.
"""
y, m, d = _ord2ymd(n)
return cls(y, m, d)
这两个方法从不同类型的事件对象中提取出年月日。我们不需要过于关注细节,只需要了解,使用类方法替代构造方法是一种重要且常见的方法。
静态方法
静态方法需要由 @staticmethod
装饰。
1
@staticmethod
但与类方法不同,不需要传入参数 cls
或 self
,自然也就不能访问类和实例里面的私有属性。简单来说,静态方法和类的关系就像寄宿家庭和寄宿学生的关系,没有血缘关系,只是住在一起。
静态方法的适用场景是,虽然不需要类里面的内容,但在逻辑上需要在类里面的方法;也适用于将一系列功能相关的函数封装在一起。
在这次的示例中,我定义一个获取名字长度的静态方法。
1
2
3
@staticmethod
def name_len(name):
return len(name)
然后在外部调用 name_len
。注意,调用静态方法时,也需要通过类名调用。
1
print(f'stu name: {stu.name}, name len: {Student.name_len(stu.name)}')
运行一下,stu 的名字是 Qiqi,长度为 4,与我们想的一致。
总结
让我们迅速回顾一下这次的内容。
类变量定义在类里面,需要通过类名访问;类方法适用装饰器 @classmethod
,第一个参数是 cls;静态方法使用装饰器 @staticmethod
,不可以访问类内部。
以上就是本次分享的全部内容啦,欢迎在评论区留下你的意见,我会尽量回复。如果觉得内容有帮助可以点个赞,如果想继续收看这类内容可以点点关注,感谢你的阅读。