skip to content
Logo Lostman_Wang的小站

python面向对象-面向对象进阶

2.3、类方法(Class Method)

常见用途
场景示例
备选构造器(工厂方法)datetime.fromtimestamp()
统计/配置 等需要类级共享数据记录实例个数、全局默认配置
在子类中自动拿到正确子类避免硬编码类名
实战示例

1.工厂方法(备用构造器)

class Person:
species = "Homo sapiens"
def __init__(self, name):
self.name = name
@classmethod
def set_species(cls, new_species):
cls.species = new_species # 修改类变量
return cls # 便于链式调用
@classmethod
def from_dict(cls, data):
return cls(data["name"]) # 工厂方法
# 使用
Person.set_species("Modern human") # 类名调用
p = Person.from_dict({"name": "Alice"}) # 工厂方法
print(Person.species, p.name) # Modern human Alice

方法内部用 cls(data["name"]) 调用了真正的构造函数 __init__,并返回新建好的实例。

调用者无需直接写 Person(...),只要传字典即可:p = Person.from_dict({"name": "Alice"})等价于p = Person("Alice")

  1. 子类继承时自动拿到正确子类
class Base:
label = "base"
@classmethod
def create(cls):
print(f"Creating {cls.label}")
return cls()
class Derived(Base):
label = "derived"
Base.create() # Creating base
Derived.create() # Creating derived

注意:如果用 Base() 硬编码,子类就得不到正确类型。

class Base:
label = "base"
@classmethod
def create(cls):
print(f"Creating {cls.label}")
return Base() # ← 写死成 Base
class Derived(Base):
label = "derived"
>>> Derived.create() # 虽然 cls 是 Derived,但永远 new Base()
Creating derived # 打印的是子类标签,可对象却是 Base
<__main__.Base object at 0x...>

在类方法里 永远不要硬编码类名去实例化,应当用 cls(...),保证“谁调用,就生成谁类型的对象”。

容易被忽略的细节
要点说明
描述器协议classmethod 对象实现了 __get__,因此只能 挂在类属性上;如果你把它赋值给实例属性,再访问时不会触发描述器,得到的只是裸函数。
不能跟 staticmethod 混用@classmethod 必须紧贴函数,顺序错会失效:
@classmethod @staticmethod → 报错或行为异常。
绑定行为类方法一旦通过 子类 访问,cls 就是 子类本身,这让它在框架代码里特别有用。
与元类配合在元类里用 classmethod 可以给 元类自身 定义类方法(此时 cls 是元类)。

一句话总结 classmethod 把函数变成 “属于类” 的方法,调用时自动把 类本身 作为第一个参数注入,最常用的场景是 工厂方法 和 需要动态拿到真实子类 的框架代码。

2.4、静态方法(@staticmethod)

常见用途
场景示例
工具函数 与类相关,但不需要访问类或实例状态数据校验、单位换算、格式转换
把旧函数“挂”进类命名空间保持代码组织结构清晰
接口/协议一致性classmethod 一起提供多态入口
实战示例

1.工具函数

class MathUtils:
pi = 3.1415926
@staticmethod
def circle_area(r):
return MathUtils.pi * r ** 2 # 显式引用类变量
@staticmethod
def is_even(n):
return n % 2 == 0
# 使用
print(MathUtils.circle_area(3)) # 28.274...
print(MathUtils.is_even(7)) # False

2.输入校验(与枚举配合)

from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Paint:
@staticmethod
def check_color(value):
if value not in Color:
raise ValueError("非法颜色")
return value
# 使用
Paint.check_color(Color.RED) # ok
Paint.check_color(99) # ValueError

3.组织代码:把全局函数收进类

import hashlib
class Utils:
@staticmethod
def md5(data: bytes) -> str:
return hashlib.md5(data).hexdigest()
@staticmethod
def sha256(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
# 使用
digest = Utils.md5(b"hello")
容易被忽略的细节
要点说明
描述器协议classmethod 一样,只能 挂在类属性上;赋值给实例属性再访问时,不会触发描述器,得到的是裸函数。
无绑定行为静态方法 不会自动传 selfcls,所以内部如果 硬编码类名,子类化时可能失效;此时应改用 classmethod
与继承的关系静态方法 不参与动态派生,子类覆盖后,父类版本 不会自动引用子类
单元测试友好因为无隐式状态,纯函数式 的静态方法很容易做单元测试。

一句话总结 staticmethod 把函数 装进类的命名空间,既不收实例也不收类,最适合写 与类逻辑相关、但完全无状态的纯工具函数。

staticmethod vs classmethod vs 实例方法

特性实例方法classmethodstaticmethod
第一个参数self(实例)cls(类)
能否访问实例属性❌(除非手动给实例)
能否访问类属性✅(需硬编码类名)
调用方式obj.f()Cls.f()obj.f()Cls.f()obj.f()
继承后动态性——✅ 自动拿到最派生类❌ 无动态性
状态依赖依赖实例依赖类无状态,纯函数

@property、@xxx.setter、@xxx.deleter 装饰器

基本语法(4 种形态)
class C:
@property
def x(self): ... # 读
@x.setter
def x(self, value): ... # 写
@x.deleter
def x(self): ... # 删
@x.getter # 极少用:覆盖读方法
def x(self): ...
最小完整示例
class Celsius:
def __init__(self, temp=0):
self._temp = temp # 真正的存储位置
@property # ① 读
def temperature(self):
"""读取摄氏度"""
return self._temp
@temperature.setter # ② 写
def temperature(self, value):
if value < -273.15:
raise ValueError("低于绝对零度")
self._temp = value
@temperature.deleter # ③ 删
def temperature(self):
print("删除温度")
del self._temp
# -------- 使用 --------
c = Celsius(25)
print(c.temperature) # 25 触发 getter
c.temperature = 100 # 触发 setter
print(c.temperature) # 100
del c.temperature # 删除温度 触发 deleter
常见用法场景
场景典型代码片段
惰性计算 / 缓存第一次访问时计算,以后直接返回缓存值
校验赋值age 必须在 0~120 之间
只读派生属性只提供 getter,不定义 setter
动态计算 / 触发事件设置坐标时自动重绘窗口
兼容重构把原本公开字段改成方法,但调用方零改动
实战进阶示例
  1. 惰性计算 + 缓存
class Circle:
def __init__(self, radius):
self.radius = radius
self._area = None # 缓存槽
@property
def area(self):
if self._area is None:
print("computing...")
self._area = 3.1415926 * self.radius ** 2
return self._area
@area.setter
def area(self, value):
raise AttributeError("can't set area")
  1. 带校验的可写属性
class Person:
def __init__(self, age=0):
self.age = age # 走 setter 校验
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not (0 <= value <= 120):
raise ValueError("age out of range")
self._age = value
  1. 兼容重构:把字段换成 property

老代码:

class Product:
def __init__(self):
self.price = 100 # 公开字段

新需求:售价必须 ≥ 0,且自动打九折。

零破坏重构:

class Product:
def __init__(self):
self._price = 100
@property
def price(self):
return self._price * 0.9
@price.setter
def price(self, value):
if value < 0:
raise ValueError("price must >= 0")
self._price = value
细节与陷阱
注意点说明
优先级别property数据描述器,实例同名属性会被 覆盖(无法直接赋值),除非实例用 __dict__ 强行绕过。
继承行为子类可 通过同名函数 + 装饰器 覆盖父 property 的某一部分(只覆盖 setter 等)。
文档字符串默认继承 getter 方法的 __doc__,可用 @x.getter 重新指定。
通过类调用Person.full_name 返回 property 自身,可用于 introspection。
性能每次访问都有一次 Python 级函数调用,对 超热路径 可手动缓存或改用 __slots__ + 描述器优化。

一句话总结 property 让你 用属性语法 做 方法事情:读、写、删、文档 全部封装在 干净的小方法 里,外部零感知,内部随便加逻辑,是 Python “裸字段 → 健壮接口” 的无痛升级钥匙。