跳到主要内容

Python 面向对象设计

类(Class)定义了类型及其行为。调用类会创建实例,而 __init__ 负责初始化这个已经创建的实例。因此,称 __init__ 为“初始化器”比“构造器”更准确。

class Account:
institution = "Example Credit Union" # 共享类属性

def __init__(self, owner: str, balance: int = 0) -> None:
if balance < 0:
raise ValueError("opening balance cannot be negative")
self.owner = owner # 实例属性
self._balance = balance

def deposit(self, amount: int) -> None:
if amount <= 0:
raise ValueError("amount must be positive")
self._balance += amount

@property
def balance(self) -> int:
return self._balance

实例方法本质上是存储在类上的函数。在属性查找过程中,实例会被绑定为第一个参数,按惯例命名为 self

状态与不变量

实例状态应保存在实例上。可变类属性会被所有实例共享,这是意外引入全局状态的常见陷阱。

方法的价值在于维护不变量(Invariant):例如 Account 可以拒绝无效存款,而不是暴露任意修改余额的能力。@property 允许在保持属性访问语法的同时加入验证或计算逻辑;但如果无需保护不变量,直接使用普通公共属性即可。

Python 中的单下划线前缀(如 _balance)仅表示“非公开”约定,并非访问控制边界。双下划线前缀会触发名称改写(Name Mangling),有助于避免子类命名冲突,但同样不提供真正的保密性。

选择最小模型

  • 普通函数:当行为不需要持久状态时,优先使用函数。
  • dataclasses.dataclass:用于记录式(Record-like)的值对象,自动生成样板代码。
  • 组合:当一个对象可以委托给另一个对象时,使用组合。
  • 继承:仅当子类型真正满足父类型的行为契约时使用,切勿仅为了复用几行代码而继承。

Python 代码通常依赖行为而非具体的继承树。只要对象支持所需的协议(Protocol),无论其具体类是什么,都可以被接受。静态类型检查器可通过 typing.Protocol 来表达这种约束。

@classmethod 接收类对象,适合实现替代构造器。@staticmethod 既不接收类也不接收实例;如果该方法与类型命名空间没有强关联,模块级函数通常更简单。

__repr____eq____len__ 等特殊方法将类型集成到 Python 数据模型中。应严格遵循文档定义的契约实现它们;当存在对应的内置操作时,不要直接调用特殊方法。

使用实例并观察属性查找

本例约定调用方以整数提供金额,单位是货币的最小单位。注解不会检查这个假设:正浮点数或 True 也会通过当前判断。接收不可信金额的边界还需显式校验类型与范围。这个类用来说明状态归属,不是完整的财务账本。

ada = Account("Ada", 100)
grace = Account("Grace")
assert ada.deposit(25) is None
assert ada.balance == 125
assert grace.balance == 0
Account.deposit(grace, 10) # 等价于 grace.deposit(10)
assert grace.balance == 10
ada.institution = "Local branch"
assert ada.institution == "Local branch"
assert grace.institution == Account.institution

普通数据属性先查实例,再查类。因此,赋值 ada.institution 会遮蔽类属性,不会修改类本身或 grace。属性装饰器遵循描述符规则:这里的属性没有 setter,所以 ada.balance = 0 会抛出 AttributeError。按 Python 的约定,直接赋值 ada._balance 仍然可行。

类教程说明了继承与方法重写。子类用 class Child(Parent): 声明基类,并可替换方法。如果子类定义了 __init__,而初始化又依赖基类逻辑,就必须调用 super().__init__(...);Python 不会自动插入这次调用。super() 按方法解析顺序寻找后续实现,而不是固定指向某个父类,这在多重继承中尤其重要。

来源

探索关联打开关联网络