在后台系统里,业务流程往往不是一条直线,而是一张充满分支与回退的网络。以电商订单为例,它要经历创建、支付、发货、完成,也可能因为用户取消或支付超时回到终止状态。如果把这些变化都写进业务函数,代码会迅速膨胀成难以维护的网状结构。Python状态机通过把状态和转移条件显式建模,让流程变得可预测、可测试。

为什么传统写法会失控
很多团队一开始会用字段加判断来处理流程。比如订单表有个status字段,每次操作前都判断当前值是否合法。这种做法在状态少的时候没问题,但状态一多,合法的转移组合就会呈指数增长。开发人员稍不注意就会写出允许从已发货退回到待支付的错误逻辑,而且这类bug通常要到线上才暴露。
下面是一段典型的脆弱代码,把所有判断堆在一个方法里:
class Order:
def __init__(self):
self.status = 'created'
def pay(self):
if self.status == 'created':
self.status = 'paid'
else:
raise ValueError('当前状态不允许支付')
def ship(self):
if self.status == 'paid':
self.status = 'shipped'
else:
raise ValueError('当前状态不允许发货')
def cancel(self):
if self.status in ('created', 'paid'):
self.status = 'cancelled'
else:
raise ValueError('当前状态不允许取消')
这段代码的问题在于,转移规则散落在每个方法内部。当新增一个“退款中”状态时,你要同时修改pay、ship、cancel多处逻辑,漏掉一处就是隐患。而且错误信息不够统一,排查问题时要翻好几个函数。
用transitions库定义状态机
transitions是Python中轻量且流行的状态机库。它允许你用字典或类属性声明状态和事件,把转移表集中管理。每个事件可以指定source(来源状态)、dest(目标状态)以及before、after回调,非常适合业务流程编排。
下面用transitions重写上面的订单流程:
from transitions import Machine
class OrderMachine:
states = ['created', 'paid', 'shipped', 'cancelled', 'finished']
def __init__(self):
self.machine = Machine(model=self, states=OrderMachine.states, initial='created')
self.machine.add_transition('pay', 'created', 'paid')
self.machine.add_transition('ship', 'paid', 'shipped')
self.machine.add_transition('cancel', ['created', 'paid'], 'cancelled')
self.machine.add_transition('finish', 'shipped', 'finished')
def on_enter_paid(self):
print('订单已支付,可以准备发货')
def on_enter_cancelled(self):
print('订单已取消,释放库存')
在上面的代码中,所有合法转移都在add_transition里一目了然。如果业务对象收到一个不合法的触发,比如对已发货的订单调用cancel,库会直接抛出MachineError,不需要你手写判断。回调方法on_enter_paid等会在进入状态时自动执行,方便做副作用处理。
这种写法的另一个好处是,你可以把状态机定义单独抽成配置文件,由产品人员维护转移图,开发只负责实现回调。流程变更时,修改states和add_transition即可,不用在业务方法里翻找分支。
在审批流中的落地示例
除了电商订单,审批流也是状态机的天然场景。一个请假单可能经历提交、主管审批、HR审批、驳回、通过。用状态机可以清晰表达“只有主管通过后才能到HR”的约束。
示例代码如下:
from transitions import Machine
class LeaveApproval:
states = ['submitted', 'manager_approved', 'hr_approved', 'rejected']
def __init__(self):
self.machine = Machine(model=self, states=LeaveApproval.states, initial='submitted')
self.machine.add_transition('manager_approve', 'submitted', 'manager_approved')
self.machine.add_transition('hr_approve', 'manager_approved', 'hr_approved')
self.machine.add_transition('reject', ['submitted', 'manager_approved'], 'rejected')
def on_enter_rejected(self):
print('请假被驳回,通知申请人')
leave = LeaveApproval()
leave.manager_approve()
leave.hr_approve()
print(leave.state)
运行后leave.state会是hr_approved,如果有人在submitte状态直接调hr_approve,程序会报错,从而强制走正确路径。相比在接口层写大量if request.role == 'hr' and leave.status == 'manager_approved',状态机让权限与流程内聚。
在真实项目中,你还可以结合数据库持久化,把model的state字段映射到表列,每次转移后保存。配合消息队列,在after回调里发通知,整个业务链路就既安全又松耦合。
状态机带来的维护优势
当业务流程需要审计时,状态机模型可以很方便地记录每一次trigger和旧新状态。你只需要包装add_transition,在转移前后写日志,就能得到完整的流转轨迹,而不用在每个业务动作里加埋点。
此外,状态机让单元测试更聚焦。你只需测“从A状态触发X事件是否到B状态”这样的用例,不必模拟整个业务上下文。对于复杂流程,这能显著降低测试代码的复杂度,也减少回归时的遗漏。
| 对比维度 | if else写法 | 状态机写法 |
|---|---|---|
| 可读性 | 转移分散,难一眼看清 | 转移集中,结构清晰 |
| 扩展性 | 加状态要改多处 | 加状态只改声明 |
| 错误防护 | 靠人工判断 | 库自动拦截非法转移 |
总体来看,Python状态机不是银弹,对于只有两三个状态的简单脚本没必要引入。但一旦业务流转出现回退、并行审批或时间触发,状态机就能把隐性规则变成显性的代码契约,让系统更稳健。
Python状态机业务流程transitions修改时间:2026-08-05 17:09:42