Python面向对象编程中,类中定义的方法可以是 @classmethod 装饰的类方法,也可以是 @staticmethod 装饰的静态方法,用的最多的还是不带装饰器的实例方法。那我们该如何区分使用它们呢?

介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A(object):
def m1(self, n):
print("self:", self)

@classmethod
def m2(cls, n):
print("cls:", cls)

@staticmethod
def m3(n):
print('111')

a = A()
a.m1(1) # self: <__main__.A object at 0x000001E596E41A90>
A.m2(1) # cls: <class '__main__.A'>
A.m3(1) # 111

类中共定义了3个方法,m1 是实例方法,第一个参数必须是 self(约定俗成的)。m2 是类方法,第一个参数必须是cls(同样是约定俗成),m3 是静态方法,参数根据业务需求定,可有可无。

实例方法self

1
2
3
4
5
6
print(A.m1)
# A.m1在py2中显示为<unbound method A.m1>
<function A.m1 at 0x000002BF7FF9A488>

print(a.m1)
<bound method A.m1 of <__main__.A object at 0x000002BF7FFA2BE0>>

A.m1是一个还没有绑定实例对象的方法,对于未绑定方法,调用 A.m1 时必须显示地传入一个实例对象进去,而 a.m1是已经绑定了实例的方法,python隐式地把对象传递给了self参数,所以不再手动传递参数,这是调用实例方法的过程。

1
2
3
A.m1(a, 1)
# 等价
a.m1(1)

类方法classmethod

1
2
3
4
5
print(A.m2)
<bound method A.m2 of <class '__main__.A'>>

print(a.m2)
<bound method A.m2 of <class '__main__.A'>>

m2是类方法,不管是 A.m2 还是 a.m2,都是已经自动绑定了类对象A的方法,对于后者,因为python可以通过实例对象a找到它所属的类是A,找到A之后自动绑定到 cls。

1
2
3
A.m2(1) 
# 等价
a.m2(1)

这使得我们可以在实例方法中通过使用 self.m2()这种方式来调用类方法和静态方法。

1
2
3
def m1(self, n):
print("self:", self)
self.m2(n)

静态方法staticmethod

1
2
3
4
5
print(A.m3)
<function A.m3 at 0x000002BF7FF9A840>

print(a.m3)
<function A.m3 at 0x000002BF7FF9A840>

m3是类里面的一个静态方法,跟普通函数没什么区别,与类和实例都没有所谓的绑定关系,它只不过是碰巧存在类中的一个函数而已。不论是通过类还是实例都可以引用该方法。

1
2
3
A.m3(1) 
# 等价
a.m3(1)

使用场景

@staticmethod

staticmethod用于修饰类中的方法,使其可以在不创建类实例的情况下调用方法,这样做的好处是执行效率比较高。当然,也可以像一般的方法一样用实例调用该方法。该方法一般被称为静态方法。静态方法不可以引用类中的属性或方法,其参数列表也不需要约定的默认参数self。我个人觉得,静态方法就是类对外部函数的封装,有助于优化代码结构和提高程序的可读性。当然了,被封装的方法应该尽可能的和封装它的类的功能相匹配。 这里给出一个样例来直观的说明一下其用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Time():
def __init__(self,sec):
self.sec = sec
#声明一个静态方法
@staticmethod
def sec_minutes(s1,s2):
#返回两个时间差
return abs(s1-s2)

t = Time(10)
#分别使用类名调用和使用实例调用静态方法
print(Time.sec_minutes(10,5),t.sec_minutes(t.sec,5))
#结果为5 5

@classmethod

classmethod好处就是你以后重构类的时候不必要修改构造函数,只需要额外添加你要处理的函数,然后使用装饰符 @classmethod 就可以

1
2
3
class C:
@classmethod
def f(cls, arg1, arg2, ...): ...

当不使用@classmethod时处理字符串格式问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class DateMethodBefore:
def __init__(self,year=0,month=0,day=0):
self.day=day
self.month=month
self.year=year

def print_date(self):
print(self.year)
print(self.month)
print(self.day)

# 对输入的字符串做处理输出
string_date = '2022-01-01'
year, month, day = map(int, string_date.split('-'))
dd = DateMethodBefore(year, month, day)
dd.print_date()
# 2022
# 1
# 1

当使用@classmethod时处理字符串格式问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class DateMethodAfter:
def __init__(self, year=0, month=0, day=0):
self.day = day
self.month = month
self.year = year

@classmethod
def deal_date(cls, string_date):
year, month, day = map(int, string_date.split('-'))
date1 = cls(year, month, day)
# 返回的是一个初始化后的class
return date1

def print_date(self):
print(self.year)
print(self.month)
print(self.day)

dd = DateMethodAfter.deal_date("2022-01-01")
dd.print_date()
# 2022
# 1
# 1

@classmethod在继承时更好用。类方法的一个主要用途就是定义多个构造器:在已经写好初始类的情况下,想给初始类再新添功能,不需要修改初始类,只要在下一个类内部新写一个方法,使用@classmethod装饰一个即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 初始类
class Date:
def __init__(self, year=0, month=0, day=0):
self.day = day
self.month = month
self.year = year

def print_date(self):
print(self.day)
print(self.month)
print(self.year)

# 新增功能
class Preprocess(Date):
@classmethod
def handle_date(cls, string_date):
# 第一个参数cls,表示调用当前的类名
year, month, day = map(int, string_date.split('-'))
after_date = cls(year, month, day)
# 返回一个初始化后的类
return after_date

S = Preprocess.handle_date("2022-1-1")
S.print_date()

# 初始类Date不需要改变,新增功能handle_date;在Preprocess类里修改即可,Preprocess继承Date初始类

@property

可以使用@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改

使用场景1:

修饰方法,使func可以像属性一样访问

1
2
3
4
5
6
7
8
9
10
11
12
class DataSet(object):
@property
def method_with_property(self): # 含有@property
return 15
def method_without_property(self): # 不含@property
return 15

l = DataSet()
print(l.method_with_property) # 15
print(l.method_without_property()) # 15
# 加@property,可以用调用属性的方式来调用方法,后面不需要加()
# 没加@property , 必须使用正常的调用方法的形式,即在后面加()

如果使用property进行修饰后,又在调用的时候,方法后面添加了(), 那么就会显示错误信息:TypeError: ‘int’ object is not callable,也就是说添加@property 后,这个方法就变成了一个属性,如果后面加入了(),那么就是当作函数来调用,而它却不是callable(可调用)的。

使用场景2:

与所定义的属性配合使用,这样可以防止属性被修改
由于python进行属性定义时,没办法设置私有属性,因此要通过@property的方法来进行设置。这样可以隐藏属性名,让用户进行使用的时候无法随意修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
class DataSet(object):
def __init__(self):
self._images = 1
self._labels = 2 #定义属性的名称
@property
def images(self): #方法加入@property后,这个方法相当于一个属性,这个属性可以让用户进行使用,而且用户有没办法随意修改。
return self._images
@property
def labels(self):
return self._labels
l = DataSet()
#用户进行属性调用的时候,直接调用images即可,而不用知道属性名_images,因此用户无法更改属性,从而保护了类的属性。
print(l.images) # 加了@property后,可以用调用属性的形式来调用方法,后面不需要加()。

参考连接:
https://zhuanlan.zhihu.com/p/28010894
https://blog.csdn.net/Liquor6/article/details/122440364
https://zhuanlan.zhihu.com/p/64487092
https://blog.sciencenet.cn/blog-3428464-1257579.html
https://cloud.tencent.com/developer/article/1597015