有同学在知识星球上问我,什么情况下使用静态方法,什么情况下使用类方法。今天我们就来捋一下这两个方法的应用场景。
首先,我们来定义一个普通的类。里面都是普通的方法。普通的方法又叫做实例方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class People: def __init__(self, name, age): self.name = name self.age = age
def introduce_myself(self): print(f'大家好,我叫: {self.name}')
def add_two_string_num(self, a, b): a_int = int(a) b_int = int(b) return a_int + b_int
def calc_age_after_n_year(self, n): age = self.add_two_string_num(self.age, n) print(f'{n}年以后,我{age}岁')
|
这个类运行起来的效果如下图所示:
大家注意在个类里面的方法add_two_string_num
,它接受两个参数,把他们转换为int
类型,然后相加并返回结果。这个过程非常简单。但是,它跟People这个类有什么直接关系吗?
其实这个方法跟这个类没有什么直接关系,我们甚至把它改成函数都可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def add_two_string_num(a, b): a_int = int(a) b_int = int(b) return a_int + b_int
class People: def __init__(self, name, age): self.name = name self.age = age
def introduce_myself(self): print(f'大家好,我叫: {self.name}')
def calc_age_after_n_year(self, n): age = add_two_string_num(self.age, n) print(f'{n}年以后,我{age}岁')
kingname = People('kingname', 20) kingname.introduce_myself() kingname.calc_age_after_n_year(10)
|
运行结果跟之前完全一样:
我们可以说,add_two_string_num
函数就是一个工具函数
。工具函数接收参数,输出结果。完全不关心谁在调用他,也不关心在哪里调用他。
但现在有一个比较尴尬的事情,这个函数,只有 People
在调用,其它地方都没有调用。单独把它放到其它地方又显得多余,弄成实例方法又浪费了self参数,这个时候,我们就可以用静态方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class People: def __init__(self, name, age): self.name = name self.age = age
def introduce_myself(self): print(f'大家好,我叫: {self.name}')
@staticmethod def add_two_string_num(a, b): a_int = int(a) b_int = int(b) return a_int + b_int
def calc_age_after_n_year(self, n): age = People.add_two_string_num(self.age, n) print(f'{n}年以后,我{age}岁')
kingname = People('kingname', 20) kingname.introduce_myself() kingname.calc_age_after_n_year(10)
|
一句话总结:静态方法就是某个类专用的工具函数。
说完了静态方法,我们再说说类方法。什么情况下应该使用类方法呢?回答这个问题前,我先返回你一个问题,怎么把People
类初始化成一个实例?
你说这还不简单吗,一行代码就行了啊:
注意,这里你在初始化这个类的时候,你是一个一个参数传入进去的。如果你用过顺丰寄送快递,你就会发现,填写收件人的时候,有两种方式,一种方式就像上线这样,一个一个参数填进去。还有一种方式,它给你一个输入框,你把一段包含姓名,地址,手机号的文字粘贴进去,它自动解析。
那么,如果我现在给你一个字符串:我的名字:青南,我的年龄:20,把它提取出来
。你怎么基于这个字符串生成People类的实例?
这个时候,你可能会这样写:
1 2 3 4 5
| import re content = '我的名字:青南,我的年龄:20,把它提取出来' name = re.search('名字:(.*?),', content).group(1) age = re.search('年龄:(\d+)', content).group(1) kingname = People(name, age)
|
这样做确实可以,但我能不能让People这个类自动识别呢?其实是可以的,有两种方法,一种方法是在__init__
里面多加几个参数,然后在初始化的时候,从这几个参数里面解析,这个方法大家都知道,我就不多讲了。我们来讲讲第二个方法,就是使用类方法。
我们只需要在定义一个类方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import re
class People: def __init__(self, name, age): self.name = name self.age = age
def introduce_myself(self): print(f'大家好,我叫: {self.name}')
@staticmethod def add_two_string_num(a, b): a_int = int(a) b_int = int(b) return a_int + b_int
@classmethod def from_chinese_string(cls, sentence): name = re.search('名字:(.*?),', content).group(1) age = re.search('年龄:(\d+)', content).group(1) return cls(name, age)
def calc_age_after_n_year(self, n): age = People.add_two_string_num(self.age, n) print(f'{n}年以后,我{age}岁')
content = '我的名字:青南,我的年龄:20,把它提取出来' kingname = People.from_chinese_string(content) kingname.introduce_myself() kingname.calc_age_after_n_year(10)
|
运行效果如下图所示:
类方法使用装饰器@classmethod
来装饰,并且它的第一个参数是隐式参数cls
。这个参数其实就是People
这个类本身。这个隐式参数在我们调用类方法的时候,是不需要传入的。在这个类方法里面,相当于使用People
初始化了一个实例,然后把这个实例返回了出去。
这样做有什么好处呢?好处就在于我们完全不需要修改__init__
,那么,也就不需要修改代码里面其它调用了People
类的地方。例如现在我又想增加从英文句子里面提取名字和年龄的功能,那么我只需要再添加一个类方法就可以了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import re
class People: def __init__(self, name, age): self.name = name self.age = age
def introduce_myself(self): print(f'大家好,我叫: {self.name}')
@staticmethod def add_two_string_num(a, b): a_int = int(a) b_int = int(b) return a_int + b_int
@classmethod def from_chinese_string(cls, sentence): name = re.search('名字:(.*?),', content).group(1) age = re.search('年龄:(\d+)', content).group(1) return cls(name, age)
@classmethod def from_english_string(cls, sentence): name = re.search('name: (.*?),', content).group(1) age = re.search('age: (\d+)', content).group(1) return cls(name, age)
def calc_age_after_n_year(self, n): age = People.add_two_string_num(self.age, n) print(f'{n}年以后,我{age}岁')
content = 'my name: kinganme, my age: 15 please extract them' kingname = People.from_english_string(content) kingname.introduce_myself() kingname.calc_age_after_n_year(10)
|
运行效果如下图所示:
一句话总结:当你想使用工厂模式,根据不同的参数生成同一个类的不同对象的时候,就可以使用类方法。
其实如果大家使用过Python自带的datetime
模块,你就会发现类方法无处不在:
1 2 3 4 5 6
| import datetime
dt0 = datetime.datetime(2021, 10, 9, 19, 10, 5) now = datetime.datetime.now() dt = datetime.datetime.fromtimestamp(1633691412) dt2 = datetime.datetime.fromisoformat('2021-10-08 19:10:05')
|
这段代码里面的.now()
、.fromtimestamp()
和 .fromisoformat()
,都是类方法。他们最终返回的都是datetime.datetime
对象。但是他们是根据不同类型的输入参数生成的。