@classmethod和@staticmethod

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huitailang1991/article/details/77895963

这里有个误区是两种方法都不是为了不想创建实例,@classmethod是为了能实现对类本身的操作,典型代表ORM中的应用,而@staticmethod是为了声明这个函数不改变实例本身的数据。

下面内容引用自本博文

普通的类方法foo()需要通过self参数隐式的传递当前类对象的实例。 @classmethod修饰的方法class_foo()需要通过cls参数传递当前类对象。@staticmethod修饰的方法定义与普通函数是一样的。

self和cls的区别不是强制的,只是PEP8中一种编程风格,slef通常用作实例方法的第一参数,cls通常用作类方法的第一参数。即通常用self来传递当前类对象的实例,cls传递当前类对象。

下文代码多来自我在Udemy课程上看的内容。

@classmethod

首先定义一个基本的Student:

#!/usr/bin/python
# coding: utf-8


class Student:
    def __init__(self, name, school):
        self.name = name
        self.school = school
        self.grades = []

    def average(self):
        return sum(self.grades) / len(self.grades)

    def go_to_school(self):
        print("I'm going to school.")


s1 = Student('Amm', 'AA')
s2 = Student('Bee', 'BB')

s1.grades = [100, 90, 80]
s2.grades = [90, 90, 90]

print(s1.average())
print(s2.go_to_school())

我们定义了一个average方法计算平均成绩,定义了一个go_to_school表示去上学的状态。对比发现:

  • average计算成绩是会用到实例自身变量self.grades的,所以该方法传入self这个参数。
  • go_to_school是没有用到任何实例变量的,那么我去掉这个self看看结果。
Traceback (most recent call last):
90.0
  File "D:/git-checkout/learn-sth-everyday/foundation/class_method.py", line 25, in <module>
    print(s2.go_to_school())
TypeError: go_to_school() takes 0 positional arguments but 1 was given

去掉了self,但是系统告知我们还是传入了一个参数,因为self是在类下面定义的方法是必须的,如果不用的话应该改为:

    def average(self):
        print("{}".format(self))
        return sum(self.grades) / len(self.grades)

    @classmethod
    def go_to_school(cls):
        print("I'm going to school.")
        print("I'm a {}".format(cls))

顺便加入了self,cls的打印,看看结果,self其实代表的是类实例的引用,cls代表的是Student这个类

<__main__.Student object at 0x031A3BF0>
90.0
I'm going to school.
I'm a <class '__main__.Student'>

这里最近看的一个关于类方法比较典型的引用就是在ORM中,比如一个User类代表的是DB中的user表,那么在定义一些操作方法的时候,往往关联的不是某个用户实例,而是要User类来操作。
那么还有一类方法是既不需要实例,也不需要类来参与运行的,这就要用到下面这个静态方法了,类似于一个全局函数。

@staticmethod

静态方法就像普通函数差不多,但是不修改实例本身的数据。静态方法和类方法都可以通过实例访问,也可以通过类访问,但是,实例方法只能通过实例访问。

继承问题 inheritance

再看下面这个例子:

class Student:
    def __init__(self, name, school):
        self.name = name
        self.school = school
        self.marks = []

    def average(self):
        return sum(self.marks) / len(self.marks)

    def friend(self, friend_name):
        return Student(friend_name, self.school)


anna = Student("Anna", "Oxford")

friend = anna.friend("Greg")
print(friend.name)
print(friend.school)


###

class WorkingStudent(Student):
    def __init__(self, name, school, salary):
        super().__init__(name, school)  # super()继承Student的init,记得带参数
        self.salary = salary


rolf = WorkingStudent("Rolf", "Harvard", 20.00)
sue = rolf.friend("Sue")
print(sue.salary)  # Error!

一个Student类和一个继承了Student的WorkingStudent类,我们在WorkingStudent的init中覆写,但是重复的内容可以直接通过super()来继承。那么问题来了,这里rolf是一个WorkingStudent,那么sue是不是也是WorkingStudent呢,我们print(sue.salary)会发现AttributeError,告知我们Student没有salary这个属性。所以sue这里其实是Student的实例,如果想一并继承的话,这里应该把friend函数改为@classmethod。代码如下:

class Student:
    def __init__(self, name, school):
        self.name = name
        self.school = school
        self.marks = []

    def average(self):
        return sum(self.marks) / len(self.marks)

    @classmethod
    def friend(cls, origin, friend_name, *args):
        return cls(friend_name, origin.school, *args)


class WorkingStudent(Student):
    def __init__(self, name, school, salary):
        super().__init__(name, school)
        self.salary = salary


rolf = WorkingStudent("Rolf", "Harvard", 20.00)
sue = WorkingStudent.friend(rolf, "Sue", 15.00)
print(sue.salary)  # This works!

改成@classmethod后,如果rolf是WorkingStudent,那么friend下面cls表示同样的,你也可以把rolf改为Student试试,这样也不影响直接通过WorkingStudent调用类方法的结果,但是如果想通过rolf.friend调用的话,就会出错,因为这样cls传入的类是Student。

猜你喜欢

转载自blog.csdn.net/huitailang1991/article/details/77895963