一、概念
在Python中,实例方法、类方法和静态方法是三种不同类型的方法,它们在定义和使用上有不同的特点和应用场景。我们在阅读一些开源的项目源码过程中,时常会看到在不同的场景中对这三种方法的调用,下面我们逐个分析这几种方法。
二、具体应用场景
1、实例方法
实例方法是最常见的方法类型,它们属于类的实例(对象),只能通过实例来调用。换句话说,当我们创建了一个类并定义类中的方法时,不加任何装饰器的状态下,这些方法都是实例方法。实例方法支持操作实例属性或调用类内的其他实例方法,也可以访问和修改实例的状态。
通过实例方法,可以将与实例相关的逻辑封装在类中,使代码更具模块化和可维护性。实例方法定义了实例的行为,这些行为通常依赖于实例的状态。也就是说,当我们定义的方法需要使用到实例的具体属性时,可以把这个方法定义为实例方法。
例如,下面这个Dog类,我们所定义的所有方法都是实例方法,这些方法可能不需要特定的入参,但基本都需要访问当前实例的属性。
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(f"{self.name} says woof!")
def get_age(self):
return self.age
def set_age(self, age):
self.age = age
# 创建实例,我们需要输入必要的参数
dog = Dog("Buddy", 3)
dog.bark() # 输出: Buddy says woof!
print(dog.get_age()) # 输出: 3
dog.set_age(4)
print(dog.get_age()) # 输出: 4
# 这里我们也可以直接修改实例的属性
dog.age = 6
print(dog.get_age())
2、类方法
类方法是属于类本身的方法,而不是某个具体的实例,它定义了与类相关的行为,这些行为通常不依赖于任何特定实例的状态。类方法的定义与实例方法没什么大的区别,只是它指向调用该方法的类对象。为了便于区分,有的人会将第一个参数设为cls,当然我们依然可以将第一个参数设置为self,只是命名不同而已。实际上,区分一个方法是否是类方法的关键在于判断该方法是不是使用@classmethod
装饰器来定义。在作用上,类方法可以访问类的状态(即类属性),或者修改类级别的属性,这些属性对所有实例共享。此外,类方法可以在没有实例化的情况下直接调用。
下面,我们同样构建一个Dog类,我们定义了两个实例方法以及两个类方法。可以看到,实例方法plus_info()可以调用类属性,但当更改当前实例的类属性时,只有当前实例会受影响。类方法change_species()则会影响Dog类,包括所有已创建的类实例。类方法create_puppy()则调用了类自身来创建实例。
class Dog:
# 初始的类属性,应当在类方法之外的全局环境定义,我们仍然能够在实例方法或者类方法中使用self.species调用该属性
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
def plus_info(self, value):
# 实例方法也可以调用全局属性
self.species += ' ' + str(value)
@classmethod
def change_species(self, new_species):
# 更改Dog类的species属性,这里会使得所有Dog实例的对应属性值发生变更
self.species = new_species
@classmethod
def create_puppy(self, name):
# 定义调用类自身来创建实例的工厂函数,这里我们预先定义了默认的年龄为3
return self(name, 3)
# 使用常规方法创建类实例
little_puppy = Dog("Bobby", 1)
print(little_puppy.name) # 输出: Bobby
print(little_puppy.age) # 输出: 1
print(little_puppy.species) # 输出: Canis familiaris
# 修改类属性,这会使得所有Dog类都拥有该属性,包括在这之前创建的实例little_puppy
Dog.change_species("Canis lupus")
print(Dog.species) # 输出: Canis lupus
# 基于类方法创建实例
puppy = Dog.create_puppy("Buddy")
print(puppy.name) # 输出: Buddy
print(puppy.age) # 输出: 3
print(puppy.species) # 输出: Canis lupus
# 打印结果可以发现,little_puppy的species属性也被同步更改了,即便它在我们修改类属性之前就已经被创建
print(little_puppy.species) # 输出: Canis lupus
# 当然,我们也可以直接修改little_puppy的实例属性还原为原来的值,但这只对当前实例有效
little_puppy.species = 'Canis familiaris'
print(little_puppy.species) # 输出: Canis familiaris
# 不过Dog类的species属性还是我们之前修改的值,这会使得后面创建的所有实例都拥有"Canis lupus"属性
print(Dog.species) # 输出: Canis lupus
# 同时,我们同样可以在实例方法中调用species进行一系列的操作,这可以更改当前实例的对应属性
little_puppy.plus_info(little_puppy.name)
print(little_puppy.species) # 输出: Canis familiaris Bobby
print(Dog.species) # 输出: Canis lupus
3、静态方法
静态方法是属于类的,但它们不依赖于类或实例的任何特定状态。静态方法没有 self
或 cls
参数,一般使用@staticmethod
装饰器来定义。使用场景上,静态方法通常用于一些不需要访问类或实例状态的工具函数,以提供一些通用的功能并在类的其他方法中使用。
下面我们创建Dog类,并定义了一个静态方法和一个实例方法,这里的静态方法is_puppy()不依赖任何类或实例属性,它可以在没有实例的情况下直接调用。
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
@staticmethod
# 这里的静态方法不需要方问类的任何属性或其他方法,它实现的是通用功能
def is_puppy(age):
return age < 1
def bark(self):
print(f"{self.name} says woof!")
# 创建实例
dog = Dog("Buddy", 3)
dog.bark() # 输出: Buddy says woof!
# 调用静态方法
print(Dog.is_puppy(0.5)) # 输出: True
print(Dog.is_puppy(2)) # 输出: False
三、总结
关于何时使用对应的方法装饰器的问题,主要得看当前所构建的方法的作用。如果一个方法完全不依赖任何类属性、实例属性,仅仅在方法内部完成了所需功能,那么这个方法就可以被定义为静态方法。而如果当前的方法不依赖于任何实例属性,但需要调用到类内的其他静态方法、类方法或者类属性时,就可以将当前的方法定义为类方法。其余情况下,默认使用实例方法即可。
当然,在很多情况下,我们把静态方法当成实例方法,不使用@staticmethod进行装饰,程序也不会报错;同样地,如果一个类方法不需要修改类属性,仅仅是调用了其他静态方法或者类方法,我们把这个类方法作为实例方法,不用@classmethod去装饰,程序同样也没有任何错误。为什么要区分这些方法并装饰它们呢,一个是为了代码的模块化和可维护性,另一个也确实是在某些需要批量修改属性的场景或者没有进行实例化的场景中,使用静态方法和类方法可以直接被调用,提高了程序的可用性。