在B站的【408实验室】所发布的《Python完全自学教程》中(https://space.bilibili.com/157232748/lists/8219076),专门讲解了类方法、静态方法和实例方法等有关内容,为了让学习者能够更深刻理解它们,再以本文专门探讨实例方法、类方法和静态方法之间的区别,以及如何判断哪种情况该用哪个。
以下是一个简单的决策规则。
观察该方法实际触及的内容:
self) → 实例方法cls)但不需要特定实例 → @classmethod@staticmethod实际有哪些使用场景?假设以下 create 方法,它不符合上述规则。它接收与 __init__ 相同的参数并直接传递。虽然它提供了一个不错的接口(Class.create(...)),但没有做任何构造函数尚未完成的工作:
# 为清晰起见进行了简化
@classmethod
defcreate(cls, amount: Decimal, currency: Currency = Currency.EUR) -> "Expense":
return cls(amount=amount, currency=currency)
当类方法完成了构造函数不应承担的工作,或从不同的起点构建对象时,它才发挥了真正的作用。加入一个归一化步骤,同一个方法便立即有了存在的意义:
@classmethod
defcreate(cls, amount: Decimal, currency: Currency = Currency.EUR) -> "Expense":
return cls(amount=amount.quantize(Decimal("0.01")), currency=currency)
@classmethod 的典型用法是作为替代构造函数。Python 不允许重载 __init__,因此当需要以多种方式构建对象时,每种方式就成为一个类方法。
标准库中提供了丰富的例子,例如 datetime.date:
date.today() # 从系统时钟构建
date.fromtimestamp(1718539200) # 从 POSIX 时间戳构建
date.fromisoformat("2026-06-16") # 从 ISO 8601 字符串构建
date.fromordinal(739418) # 从预推格里高利历序数构建
date.fromisocalendar(2026, 25, 1) # 从 ISO 年/周/日构建
源码:
# 附加构造函数
@classmethod
deffromtimestamp(cls, t):
"从 POSIX 时间戳(例如 time.time())构建日期。"
if t isNone:
raise TypeError("'NoneType' object cannot be interpreted as an integer")
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
return cls(y, m, d)
@classmethod
deftoday(cls):
"从 time.time() 构建日期。"
t = _time.time()
return cls.fromtimestamp(t)
...
...
以上每个方法都返回一个 date,但使用的原始材料各不相同。它们必须是类方法,因为需要 cls 来构造实例,并且返回 cls(...) 也使得子类能够正常工作。例如,如果 MyDate 是 date 的子类,那么 MyDate.today() 将返回 MyDate 实例,而不是 date。
你会在整个生态中看到相同的模式:dict.fromkeys(...)、int.from_bytes(...),以及 Pydantic 中的 Model.model_validate(...) / model_validate_json(...) 都是类方法,它们从不同的原始材料构建实例。
另一个 classmethod 的用例是类级状态:注册表、缓存、计数器。插件注册表是一个清晰的例子,因为该方法读取并修改的是属于类而非任何实例的状态:
classHandler:
_registry: dict[str, type["Handler"]] = {}
@classmethod
defregister(cls, name: str, handler: type["Handler"]) -> None:
cls._registry[name] = handler
@classmethod
defget(cls, name: str) -> type["Handler"]:
return cls._registry[name]
# 在类上调用,无需实例;它修改的是存活在类上的状态
Handler.register("json", JSONHandler)
如果方法既不触及 self 也不触及 cls,它就是一个静态方法,即一个恰好因命名空间而位于类内部的普通函数。当辅助函数与类紧密耦合,且你希望 Expense.normalize(...) 读起来顺畅时,这是合理的选择。此时它成为类 API 的一部分(会出现在 dir(Expense) 中),且无需实例即可调用。
真正的静态方法比前两者更为少见,这本身就能说明一些问题。一个清晰的例子是带有颜色转换辅助方法的 Color 类:
classColor:
def__init__(self, name: str):
self.name = name
self.rgb = COLOR_NAMES.get(name.upper())
@staticmethod
defhex2rgb(hex_value: str) -> tuple[int, int, int]:
return tuple(int(hex_value[i:i + 2], 16) for i in (1, 3, 5))
@staticmethod
defrgb2hex(rgb: tuple[int, int, int]) -> str:
returnf"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"
hex2rgb 和 rgb2hex 既不触及实例也不触及类。它们是纯粹的函数式转换,位于 Color 上,使得 Color.hex2rgb("#ff0000") 在与 API 其他部分并列时读起来很自然。
但这正是值得注意的信号:静态方法可能只是伪装成方法的函数,有时更诚实的做法是将其提取为模块级函数,这样更容易测试和独立使用。
self | |||
@classmethod) | cls | ||
@staticmethod) |
当自己编写代码时,你几乎不会无理由地添加一个方法。而当智能体(agent)编写代码时,你会得到一个看似合理却未经人为选择的结构:一个什么也不做的 create 类方法,一个本该是独立函数的静态方法,一个挂载在错误类上的辅助方法。这就需要你做出判断:该方法所完成的工作是否真正属于该类,还是这仅仅是智能体从其他代码中学到的一种模式?
放慢速度,以批判的眼光审视任何代码并提出这些问题,是值得的。随着 AI 更快地产出更多代码,我们很容易认为“看起来像 Python 就是好的 Python”。但智能体没有品味,它会欣然生成技术上正确但结构上错误的代码。
这也正是撰写此文章的原因:为你提供一个简单的决策规则,让你在审查时能在脑海中运行。
因此,请使用 AI,但要持续培养自己的知识和品味。你懂得越多,就越能更好地评判摆在你面前的代码——无论它是由人还是由智能体写的。