跳到正文

05 面向对象编程 ​

一句话:类把状态和行为绑在一起,特殊方法让对象接入语言语法;实例方法收 self,类方法收 cls,静态方法不收隐式参数,三者职责不能混。

适用:CPython 3.11+。dataclass 需要 3.7+。

为什么需要 ​

零散变量和参数会让调用方承担所有约束;类把状态和「必须同时修改」的规则收进方法。继承用于表达可替换接口,多态让调用方只依赖行为;但继承层次过深会把耦合藏起来,所以工程上优先组合。特殊方法解决另一个问题:让自定义对象像内建类型一样支持 +、len()、下标和 with。

核心机制 ​

类属性、实例属性与属性查找 ​

python
class Model:
    version = "v1"          # 类属性:所有实例共享
    def __init__(self, name):
        self.name = name    # 实例属性:每个实例独立

m = Model("mlp")
print(m.name, m.version)            # mlp v1
m.version = "v2"                    # 实例上新建属性,不改类属性
print(m.version, Model.version)     # v2 v1

属性查找先找实例 __dict__,再沿类与 MRO 找。类属性是共享默认值;可变类属性(如 registry = [])也是共享的,想让每个实例独立,必须在 __init__ 里赋值。

三种方法:self、cls 与静态方法 ​

python
class Dataset:
    total = 0

    def __init__(self, name):
        self.name = name
        type(self).total += 1       # 用实际类,避免子类共用基类计数

    def describe(self):             # 实例方法:第一个参数 self
        return self.name

    @classmethod
    def from_name(cls, name):       # 类方法:第一个参数 cls,收到的是类
        return cls(name)

    @staticmethod
    def valid_size(n):              # 静态方法:没有隐式参数
        return n > 0

d = Dataset.from_name("train")
print(d.describe(), Dataset.valid_size(10), Dataset.total)   # train True 1
类型第一个参数绑定到能做什么典型用途
实例方法self实例访问实例属性、类属性常规行为
类方法cls类访问类属性;cls(...) 构造备选构造函数、类级状态
静态方法无无只能用显式传入的参数与类相关的纯工具函数
  • 实例方法通过实例调用,m.f(x) 等价于 Model.f(m, x);通过类调用时必须显式传实例。
  • 类方法通过类或实例都能调用,但第一个参数始终是类;子类调用 Sub.from_name() 时 cls 是 Sub。它没有 self,不能直接访问某个实例的属性。
  • 静态方法既不收 self 也不收 cls,放在类里只是为了命名空间。

继承、MRO 与组合 ​

python
class Base:
    def predict(self, x): raise NotImplementedError
class Tree(Base):
    def predict(self, x): return "tree"
class Net(Base):
    def predict(self, x): return "net"
print(Tree().predict(1), Net().predict(1))    # tree net

多态靠「有同名方法」即可,不要求共同基类(鸭子类型)。多继承按 C3 线性化确定 MRO,super() 沿 MRO 走,不是简单「找父类」。工程上优先组合:把能力做成独立对象持有,而不是堆继承。确需多继承时,mixin 只放行为、不放状态,并用 D.__mro__ 验证顺序。

特殊方法:让对象接入语言 ​

python
class Vector:
    def __init__(self, x, y): self.x, self.y = x, y
    def __repr__(self): return f"Vector({self.x}, {self.y})"
    def __add__(self, o): return Vector(self.x + o.x, self.y + o.y)

v = Vector(1, 2)
print(v + v)                                  # Vector(2, 4)

__repr__ 面向调试,__str__ 面向用户;__len__、__getitem__ 分别接入 len() 和下标;__enter__/__exit__ 支持 with(06)。__eq__ 定义后 __hash__ 会被设为 None,对象不再可哈希,需要放进 set/dict 时要同时实现 __hash__。

property 与 dataclass ​

python
from dataclasses import dataclass, field
class Temperature:
    def __init__(self, c): self._c = c
    @property
    def celsius(self): return self._c
@dataclass
class Config:
    name: str = "bert"
    tags: list[str] = field(default_factory=list)   # 可变默认值必须用 field

print(Temperature(25).celsius, Config(tags=["nlp"]))

property 把校验逻辑藏进属性访问,调用方仍写 t.celsius;dataclass 自动生成 __init__、__repr__、__eq__,适合配置和 DTO。可变默认值必须用 field(default_factory=...),原因和 04 的可变默认参数相同。

最小上手 ​

保存为 oop.py 后运行,预期输出 train:100 0 True:

python
from dataclasses import dataclass, field

@dataclass
class Dataset:
    name: str
    size: int
    tags: list[str] = field(default_factory=list)
    def describe(self): return f"{self.name}:{self.size}"
    @classmethod
    def from_name(cls, name): return cls(name, 100)
    @staticmethod
    def valid(size): return size > 0

d = Dataset.from_name("train")
print(d.describe(), len(d.tags), Dataset.valid(10))

生产动作 ​

  • 状态与构造:实例状态放 __init__,类级常量放类属性;可变类属性要么避免,要么明确它就是共享状态;需要多种构造方式时用 @classmethod。
  • 继承:优先组合;多继承只用于 mixin,职责单一;super().__init__() 不要漏。
  • 排障三段:
    • 现象:改了 a 的属性,b 也变 → 命令:print(a is b)、检查类属性是否可变 → 判断:是否共享了类级可变对象。
    • 现象:子类计数把基类也算进去 → 命令:print(type(self), cls) → 判断:是否硬编码了基类名。
    • 现象:TypeError: unhashable type → 命令:检查 __eq__ 与 __hash__ → 判断:对象放进 set/dict 前需要可哈希。

边界 ​

  • 类不是模块化的唯一手段;无状态工具函数放模块级更简单。多继承和元类在业务代码里很少需要。
  • dataclass 不做运行时类型校验;需要校验用 Pydantic v2(09)。

讲清楚 ​

类是状态和行为的封装:实例方法收 self、绑定具体对象;类方法收 cls、绑定到类,适合备选构造函数和类级状态;静态方法没有隐式参数,只是挂在类命名空间下的工具函数。三者混淆的典型后果是:在类方法里访问实例属性、在静态方法里访问类变量、子类计数写死基类名。继承表达可替换行为,多态靠鸭子类型,但工程上优先组合。特殊方法让对象支持 +、len()、下标和 with,property 与 dataclass 则把校验和样板代码收进声明。

适用 CPython 3.11+ | 初稿 2026-08,2026-10 压缩并订正三种方法

把一门技术讲透到既懂也会用