likes
comments
collection
share

classmethod用法

作者站长头像
站长
· 阅读数 23

在讲解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
评论
请登录