06 面向对象与数据类
大约 3 分钟编程语言Python面向对象dataclassProtocol
06 面向对象与数据类
类不是为了装函数。类适合封装状态与行为的约束;只有数据、没有行为时,优先考虑数据类、命名元组或普通字典。
1. 基本类
class RetryPolicy:
def __init__(self, attempts: int, base_delay: float = 0.5) -> None:
if attempts < 1:
raise ValueError("attempts must be positive")
self.attempts = attempts
self.base_delay = base_delay
def delay_for(self, attempt: int) -> float:
return self.base_delay * (2 ** (attempt - 1))实例属性通常在 __init__ 中建立。类属性适合常量和不随实例变化的配置。
2. dataclass
from dataclasses import dataclass, field
@dataclass(slots=True)
class CheckResult:
name: str
ok: bool
details: list[str] = field(default_factory=list)
result = CheckResult(name="disk", ok=True)
result.details.append("root filesystem below threshold")
print(result)要点:
- 可变默认值必须使用
field(default_factory=...)。 frozen=True生成不可变实例,适合值对象和字典键。slots=True可减少实例字典开销,但会限制动态新增属性。order=True可以让数据类参与排序,但比较字段必须合理。
3. 值对象
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class Endpoint:
host: str
port: int
def __post_init__(self) -> None:
if not self.host:
raise ValueError("host is required")
if not 1 <= self.port <= 65535:
raise ValueError("invalid port")值对象适合表达地址、端口、版本、金额和资源标识。校验在对象构造时完成,后续代码可以依赖不变量。
4. 属性与计算字段
from dataclasses import dataclass
@dataclass(slots=True)
class Metric:
success: int
total: int
@property
def success_rate(self) -> float:
if self.total == 0:
return 0.0
return self.success / self.total属性适合无副作用的计算。不要用 property 偷偷执行网络请求、修改其他对象或隐藏昂贵操作。
5. Protocol 与抽象边界
from typing import Protocol
class Notifier(Protocol):
def send(self, title: str, message: str) -> None:
...
def notify_all(notifiers: list[Notifier], message: str) -> None:
for notifier in notifiers:
notifier.send("ops", message)Protocol 表达结构化接口:对象只要具备所需方法就能使用,不要求继承。它适合测试替身和可插拔依赖。
抽象基类适合确实需要共享实现或运行时 isinstance 检查的场景:
from abc import ABC, abstractmethod
class Exporter(ABC):
@abstractmethod
def export(self, rows: list[dict[str, object]]) -> bytes:
raise NotImplementedError6. 组合优先
继承表达“是一个”,组合表达“使用一个”。运维工具中大量行为更适合组合:
class DatabaseCheck:
def __init__(self, connector):
self.connector = connector
def run(self) -> bool:
return self.connector.ping()把连接、重试、日志、缓存拆为可替换协作者,比建立深层继承树更容易测试。
7. Enum
from enum import StrEnum
class Severity(StrEnum):
INFO = "info"
WARNING = "warning"
CRITICAL = "critical"
print(Severity.CRITICAL.value)枚举适合有限且稳定的状态集合。外部系统允许扩展的开放值集合不一定适合硬编码为 Enum。
8. 常见陷阱
- 为每个小函数创建类,增加无意义层级。
- 继承只为复用代码,导致子类依赖父类实现细节。
__eq__和__hash__不一致。- 重载运算符但破坏直觉或返回意外类型。
- 在
__del__中执行关键资源清理;优先使用上下文管理器。 - 用
getattr和动态属性绕过明确接口。
