引言
前面的文章中介绍了关于面向对象中类的定义及属性的相关使用。关于类的属性的介绍,暂时可以告一段落。今天准备聊一下类封装中另外的一个构成部分,也就是方法。
关于类中的方法,可以理解为函数在特定类中的封装,会函数的定义,基本就会了类中方法的定义。
除了满足基本函数的使用外,类中根据不同的场景,将封装其中的方法分为三种类型:实例方法、类方法,以及静态方法。
需要注意的是,当我们说到函数时,一定是在模块中独立定义的函数;而我们提到方法时,一定是特指在类中定义的“函数”。
本文主要就这三种方法依次展开介绍。
茴香豆的“茴”有四种写法
鲁迅先生刻画的孔乙己一个典型的情节是,孔乙己以“茴香豆的茴字有四种写法”来安慰和支撑那少的可怜的读书人的自尊,这种掉书袋之嫌,似乎讽刺了一些自欺欺人的无用之学。
但是,在笔者看来,没有绝对意义上的无用之学。所谓的有用、无用,其实是混淆了主客体的相互作用的关系。任何知识、技能,只要找到了其适用的场景,都可以是可能有用的。以宣称客体的无价值、无用来掩饰主体的无能,其实是很没有道理的。
面向对象中的方法,也存在这样的问题。类中的不同方法的定义及使用,有时也不免被抨击有无用之嫌。类中的方法有三种写法,分别是:实例方法、类方法、静态方法。实例方法当然是最常用的,但是类方法和静态方法也有其存在的意义及适用的场景。
类似于类属性和实例属性的使用,方法的定义与使用,其实也依赖于对需求的分析与理解,以及面向对象的设计。有用与无用的问题,其实转变为当前需求中有无使用的场景。抛开场景谈有用无用,其实都是不负责任的耍流氓。
实例方法
实例方法的定义非常简单,只要把函数定义放到类定义中即可。但是,需要注意的是传参的一个规则:第一个参数必须是对象的引用,而且这个参数的传参由Python解释器自动完成,我们自己进行调用时,只需要传入除第一个参数之外的参数即可。
还是以打工人的代码为例:
class DaGongRen:
# 类属性,打工人计数
num = 0
def __init__(self, name, gender):
# 实例属性
self.name = name
self.gender = gender
DaGongRen.num += 1
def work(self):
print(f"打工人【{self.name}】[{'男' if self.gender == 1 else '女'}]】在努力工作")
if __name__ == '__main__':
dgr = DaGongRen('张三', 1)
dgr.work()
代码中:__init()、work()都是实例方法,直接在类中定义,方法的首个参数:self表示的是实例对象的引用,该参数无需手动传递,Python解释器自动完成。此外,self形参名也是习惯约定,可以是其他名字,但是会让阅读代码的人觉得比较奇怪。在PyCharm等IDE中定义实例方法时,会自动补全self的形参定义。
实例方法的调用,如同实例属性一样,通过对象名.方法名()进行调用。
实例方法是类中最常用的方法类型,主要用于访问或者修改实例属性,或者调用其他实例方法间接访问或者修改实例属性。
类方法
类似于类属性的定位,类方法也是在类中直接定义,但是,如果只是这样定义,无法与实例方法区分。Python中的解决思路是,通过给函数添加classmethod装饰器来标识一个方法为类方法,同样以代码举例说明:
class DaGongRen:
# 类属性,打工人计数
num = 0
# 类属性,team
team = '打工人俱乐部'
def __init__(self, name, gender):
# 实例属性
self.name = name
self.gender = gender
DaGongRen.num += 1
def work(self):
print(f"打工人【{self.name}[{'男' if self.gender == 1 else '女'}]】在[{DaGongRen.team}]中努力工作")
@classmethod
# 类方法,用于清除实例对象的计数
def clear_num(cls):
cls.num = 0
@classmethod
# 统一更新实例对象的组织信息
def change_team(cls, new_team):
print(f"组织更名: {cls.team} -> {new_team}")
cls.team = new_team
if __name__ == '__main__':
zs = DaGongRen('张三', 1)
zs.work()
ls = DaGongRen('李四', 2)
ls.work()
# 组织更名
DaGongRen.change_team('打工人协会')
zs.work()
ls.work()
执行结果:
类方法的定义需要注意两点:
1、需要通过@classmethod装饰器来标识一个方法为实例方法
2、如同实例方法第一个参数为实例对象的引用,类方法也有类似的规定,类方法的第一个参数为类对象的引用,该参数也由Python解释器自动完成参数的传递,无需手动传参。
类方法的适用场景主要有:
1、由于实例对象只能访问类属性,无法修改类属性,所以,当修改类属性时,则需要选择类方法。
2、当我们需要支持通过不同的方式进行实例对象的构造时,可以通过定义类方法来实现,常见于设计模式中的工厂方法等。
3、当有些行为是与类整体有关,而不是跟特定实例相关时,也应该选择使用类方法。(当然,涉及到类属性时,很多时候都是跟第1点是相关的)
静态方法
静态方法是相对少见的一类方法,但也有其适用的场景。当我们需要实现一些与类和实例对象本身业务逻辑无关的功能时,通常是一些工具方法,又不涉及到类属性或者实例属性的访问修改时,可以考虑静态方法。静态方法需要通过staticmethod装饰器来标识,参数定义上无特殊规则。抛开装饰器不说,其实就是一个普通的函数,只是定义在类中。比如,我们希望有一个工具方法能够实现打工人性别从数字到文字的转换:
class DaGongRen:
# 类属性,打工人计数
num = 0
# 类属性,team
team = '打工人俱乐部'
def __init__(self, name, gender):
# 实例属性
self.name = name
self.gender = gender
DaGongRen.num += 1
@staticmethod
# 静态方法,主要实现字符串的映射转换
def gender_map(gender):
if gender == 1:
return '男'
elif gender == 2:
return '女'
return '未知'
def work(self):
print(f"打工人【{self.name}[{self.gender_map(self.gender)}]】在[{DaGongRen.team}]中努力工作")
@classmethod
# 类方法,用于清除实例对象的计数
def clear_num(cls):
cls.num = 0
@classmethod
# 统一更新实例对象的组织信息
def change_team(cls, new_team):
print(f"组织更名: {cls.team} -> {new_team}")
cls.team = new_team
从实现方式上,可以把静态方法单独拿出来,作为模块中的一个工具函数的定义。之所以,放在类中定义,一方面是代码的封装的考虑;另一方面虽说不如类方法、实例方法跟业务需求比较紧密,静态方法还是有些弱的关联的。
总结
本文以孔乙己的情节为例,简单论述了笔者关于有用无用的问题的看法,在笔者看来,没有无用的知识,只是必要性以及适用场景的发现与匹配的问题。
然后,介绍了面向对象中三种方法的定义及适用场景。
关于编程的自学的方法,其中很关键的一块就是首先知道有这个东西,然后在遇到实际的需求时,才会知道有这种方案的存在,然后才是通过实践进一步真正掌握这些用法。
感谢您的阅读,如果对您能稍微有一点点帮助,那是再好不好了。