0%

Python3 静态方法和类方法:面向对象编程的高级工具

Python 的 @classmethod 和 @staticmethod:什么时候用哪个,一次说清楚

写 Python 类的时候,除了普通的实例方法,还有两种带装饰器的方法:@classmethod@staticmethod

刚接触的时候很容易搞混——“不都是类里的方法吗,为什么还要分三种?”

其实区别很简单:实例方法要操作对象,类方法要操作类,静态方法只是”恰好放在这个类里的普通函数”。

先复习一下实例方法:带 self 的那个

1
2
3
4
5
6
7
8
class Person:
species = "人类"

def __init__(self, name):
self.name = name

def greet(self):
return f"你好,我是 {self.name}"

实例方法的特点:

  • 第一个参数是 self,指向具体的对象
  • 能访问对象的属性(self.name
  • 能访问类的属性(self.speciesPerson.species
  • 必须通过对象调用

实例方法处理的是”每个对象各自不同的数据”,比如每个人的名字不同。

类方法:@classmethod,操作的是类本身

类方法的第一个参数是 cls,指向类本身,而不是某个具体对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person:
count = 0

def __init__(self, name):
self.name = name
Person.count += 1

@classmethod
def get_count(cls):
return cls.count # 返回总人数,跟具体对象无关

# 通过类名调用
Person.get_count() # 0

类方法能访问和修改类属性(所有对象共享的数据),但不能访问实例属性(每个对象各自的数据)。

什么时候用类方法?三种最常见的场景:

场景1:工厂方法——用不同方式创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from datetime import datetime

class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day

@classmethod
def from_string(cls, date_str):
"""从 '2024-01-15' 这样的字符串创建日期对象"""
year, month, day = map(int, date_str.split("-"))
return cls(year, month, day)

@classmethod
def today(cls):
"""创建今天的日期"""
now = datetime.now()
return cls(now.year, now.month, now.day)

# 用不同方式创建
d1 = Date.from_string("2024-01-15")
d2 = Date.today()

这种”替代构造函数”的模式,是类方法最典型的用途。

场景2:操作类属性(共享状态)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class GameConfig:
difficulty = "normal"

@classmethod
def set_difficulty(cls, level):
valid = ["easy", "normal", "hard"]
if level in valid:
cls.difficulty = level

@classmethod
def get_difficulty(cls):
return cls.difficulty

# 修改的是类的属性,所有地方都生效
GameConfig.set_difficulty("hard")
print(GameConfig.get_difficulty()) # hard
场景3:继承场景下,cls 会自动指向子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal:
@classmethod
def create(cls, name):
return cls(name)

class Dog(Animal):
def __init__(self, name):
self.name = name

class Cat(Animal):
def __init__(self, name):
self.name = name

# cls 会指向调用时的类
dog = Dog.create("旺财") # cls = Dog
cat = Cat.create("咪咪") # cls = Cat

如果在类方法里硬编码了类名(比如 return Animal(name)),子类调用的时候就会返回父类的实例,而不是子类的。cls 而不是写死类名,是类方法支持继承的关键。

静态方法:@staticmethod,就是普通函数放在类里

静态方法没有 self 也没有 cls,它就是个普通函数,只是恰好在类的命名空间里。

1
2
3
4
5
6
class MathUtils:
@staticmethod
def add(a, b):
return a + b

MathUtils.add(2, 3) # 5

静态方法不能访问实例属性,也不能访问类属性(除非把类名传进去,但那样很别扭)。

什么时候用静态方法?它的价值在于”归类”——把相关但不需要访问类/实例状态的函数放在一起。

场景1:工具函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Validator:
@staticmethod
def is_email(email):
import re
pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
return bool(re.match(pattern, email))

@staticmethod
def is_phone(phone):
import re
return bool(re.match(r'^1[3-9]\d{9}$', phone))

# 用类名调用,语义清晰
Validator.is_email("test@example.com")
Validator.is_phone("13812345678")

如果把这两个函数放在模块顶层也行,但放在 Validator 类里,代码组织更清晰——别人一看就知道”这些是验证相关的工具”。

场景2:数据转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class UnitConverter:
@staticmethod
def celsius_to_fahrenheit(c):
return c * 9/5 + 32

@staticmethod
def km_to_miles(km):
return km * 0.621371

@staticmethod
def bytes_to_human(size):
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024:
return f"{size:.1f} {unit}"
size /= 1024
return f"{size:.1f} TB"

UnitConverter.celsius_to_fahrenheit(25) # 77.0

欢迎关注我的其它发布渠道