这里有个误区是两种方法都不是为了不想创建实例,@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。