classmethod用法
在讲解classmethod装饰器用法之前,我们首先需要了解Python类里面存在的方法类型,主要涉及以下三种:
实例方法 静态方法 类方法
关于实例方法,指的是与Python中类的实例具有强烈相关性的方法,不同的实例执行情况是不一致的,且一般需通过实例对象来执行该方法,如下:
class student:
"""
PrintHelloName 和 PrintAge 都属于实例方法
特点:
1. 需要通过实例对象调用
2. 不同的实例对象调用结果不同
3. 第一个参数默认为self(也可以改为其他名称,效果一样),指代实例对象本身
"""
def __init__(self, name, age):
self.name = name
self.age = age
def PrintHelloName(self):
return f"hello {self.name}"
def PrintAge(self):
return self.age
if __name__ == "__main__":
S1 = student("张三", 20) # 需要先创建出实例对象
S2 = student("李四", 18)
print(S1.PrintHelloName()) # 通过实例对象进行调用,且不同对象的调用结果也是不一样的
print(S2.PrintHelloName())
# print(student.PrintAge()) # 直接通过类调用会报错
静态方法,则是通过@staticmethod装饰的方法,该方法不需要默认参数self,无法调用实例中的属性,可以通过类直接调用而不需要进行实例化对象,如下:
class people:
age = 18
def __init__(self, name, age):
self.name = name
self.age = age
@staticmethod
def PrintAge():
return people.age # 这个变量是可以访问的,因为是类属性
@staticmethod
def PrintName():
return people.name # 这里是不可访问的,因为是实例属性
if __name__ == '__main__':
print(people.PrintAge()) # 可以直接通过类调用这个方法,而不需要进行初始化
# print(people.PrintName()) # 无法访问实例属性和实例方法,因为没有创建实例
P = people("张三", 20)
print(P.PrintAge()) # 也可以通过实例进行调用,但是仍然无法获取到实例属性,因为没有self这个参数
类方法则是通过通过@classmethod来装饰的方法,它的第一个参数代表的是类本身,也可以通过非实例化的方式进行调用
class people:
name = "张三"
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def PrintName(cls):
return cls.name
if __name__ == "__main__":
print(people.PrintName())
这里可以看到,类方法和静态方法一样,也可以以类的方式调用。接下来将详细讲解类方法或者说@classmethod的作用。
关于cls
cls默认指代当前类本身
如何确定cls默认指代的是类本身,而不是实例或者其他的内容呢?首先我们先看这样一段代码
class people:
name = "张三"
age = 18
def __init__(self, age, name):
self.name = name
self.age = age
@classmethod
def getClass(cls):
return f"People({cls.name}, {cls.age})"
def GetClass(self):
return f"People({self.name}, {self.age})"
if __name__ == "__main__":
P = people("李四", 20)
print(P.getClass())
print(P.GetClass())
请判断这段代码的打印结果,如果cls指代的是实例的话,那么从逻辑上来看,这两段代码的打印应该是相同的,但是从执行结果来看,二者是不同的
执行结果: People(张三, 18) People(李四, 20)
从这段打印结果上其实就可以看出来,self指代的是实例本身,因为在创建实例的时候,传入的就是李四
和20
,那么如何更进一步判断,self指向的实例,cls指向的是类本身呢?接下来我们将通过python自带的id()函数来判断
class people:
name = "张三"
age = 18
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def getClass(cls):
print("cls地址:", id(cls))
def GetClass(self):
print("self地址:", id(self))
if __name__ == "__main__":
P = people("李四", 20)
print("实例P的地址:", id(P))
P.GetClass()
P.getClass()
print("对象people地址:", id(people))
# output
"""
实例P的地址: 2159950259648
self地址: 2159950259648
cls地址: 2160365674480
对象people地址: 2160365674480
"""
id函数用来判断对象在内存中的地址,当调用某个对象时,会先在内存中找到这个对象然后在进行调用,所以两个对象在内存中的地址一样时,那么,二者一定是同一个对象,当你通过某个对象修改其中的属性时,另外一个内存相同的对象也会发生改变,这里通过验证改变cls.name
来观察people.name
是否改变,来验证cls和people是一模一样的对象
class people:
name = "张三"
age = 18
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def SetName(cls, name):
cls.name = name
if __name__ == "__main__":
print(people.name)
people.SetName("李四")
print(people.name)
通过这段代码,可以有效的确定,cls就是people这个类,那么说了这么一堆,@classmethod究竟有什么作用呢?
@classmethod的作用
在__init__方法之外编写多个独立的函数来处理不同的实例创建逻辑
例如,某个需求如下:
用户信息分为: 姓名、年龄、性格、收入、性别、身高、学历,其中男性用户需要保存姓名、年龄、收入、性别、身高,女性用户需要保存姓名、年龄、性格、性别、学历
如果通过常规方式,代码如下:
class people:
def __init__(self, name=None, age=None, nature=None, revenue=None, sex=None, height=None, Degree=None):
if sex == "男":
self.name = name,
self.age = age
self.nature = nature # 收入
self.sex = "男"
self.height = height
elif sex == "女":
self.name = name
self.age = age
self.revenue = revenue # 性格
self.sex = "女"
self.Degree = Degree
else:
raise TypeError("Gender abnormalities")
这里证明了@classmethod并不是一定需要,__init__方法也可以实现,但是比较繁琐,而且如果未来要对男性客户和女性客户做一些其他操作(例如男性用户年龄+18,女性用户年龄-18,男性用户收入换算为年,女性用户身高换算为厘米....)如果都在__init__操作的话,会导致这个函数异常繁琐,极其影响观感。 如果使用@classmethod则可以如下操作。
class people:
def __init__(self, name=None, age=None, nature=None, revenue=None, sex=None, height=None, Degree=None):
self.name = name
self.age = age
self.nature = nature
self.revenue = revenue
self.sex = sex
self.height = height
self.Degree = Degree
@classmethod
def setMan(cls, name, age, nature, sex, height):
if sex != "男":
raise TypeError("Gender abnormalities")
return cls(name, age, nature, sex, height)
@classmethod
def setWoMan(cls, name, age, revenue, sex, Degree):
if sex != "女":
raise TypeError("Gender abnormalities")
return cls(name, age, revenue, sex, Degree)
通过这样的方式可以将不同的实例来区分开,如果未来要对男性用户修改,则修改setMan函数即可,女性同理,这样做也可以让对应的功能更简洁,后续维护的过程中更容易分析。
关于子类
哪个类运行的类方法,cls则代表那个类
看下面这段代码
class people:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def SetPeople(cls, name, age):
return cls(name, age - 1)
class Man(people):
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
if __name__ == "__main__":
P = Man.SetPeople("李四", 22)
print(P.age)
如果按逻辑推断,这段代码执行应该是失败的,因为我是通过子类来调用的SetPeople方法,此时cls应该代表的是子类,也就是Man,由于cls(name, age - 1)
缺少了sex
这个参数,所以结果应该是失败的,运行结果也确实符合预期.
TypeError: Man.init() missing 1 required positional argument: 'sex'
子类能否不用装饰器呢?
class people:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def SetPeople(cls, name, age):
return cls(name, age - 1)
class Man(people):
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def SetPeople(self):
return f"Man({self.name}, {self.age})"
if __name__ == "__main__":
P = Man("李四", 22, "男")
print(P.SetPeople())
# output
# Man(李四, 22)
通过这段代码可以看出来,子类确实可以重载这个方法,而且并不会因为父类该方式是类方法,子类也不允许改变?子类可以将类方法变为实例方法或者静态方法,从而改变父类方法的执行逻辑。
转载自:https://juejin.cn/post/7383140180562067491