3、Python实现状态机:面向对象的状态模式、使用字典驱动状态机、第三方库transitions介绍

好,咱们进入实战环节。

前面两章讲了理论,讲了协议拆解。但说真的,纸上谈兵没意思。我当年刚入行的时候,看了一堆状态机论文,结果写出来的代码又臭又长。后来才明白——状态机的实现,其实就三种主流玩法。

今天我把它们全盘托出。

3.1 面向对象的状态模式

这是最正统的玩法。设计模式里的State Pattern,说白了就是把每个状态封装成一个类。

为什么要这么做?

你想想看,在交易所协议解析里,每个状态的行为差异很大。比如「等待登录」状态和「已登录」状态,对同一个消息的处理逻辑完全不同。用if-else硬写?代码会变成一坨浆糊。

核心思想:状态本身也是对象。每个状态对象负责自己的行为逻辑。

我在项目中遇到过最典型的场景——FIX协议的会话层。登录、心跳、重连、登出,每个状态的处理逻辑都不一样。用状态模式,代码结构清晰得像教科书。

from abc import ABC, abstractmethod

class SessionState(ABC):
    """会话状态基类"""
    
    @abstractmethod
    def on_message(self, context, msg):
        pass
    
    @abstractmethod
    def on_timeout(self, context):
        pass

class LoggingInState(SessionState):
    """登录中状态"""
    
    def on_message(self, context, msg):
        if msg.type == 'Logon':
            print("登录成功,切换到已登录状态")
            context.set_state(LoggedInState())
        else:
            print("登录失败,保持登录中状态")
    
    def on_timeout(self, context):
        print("登录超时,切换到断开状态")
        context.set_state(DisconnectedState())

class LoggedInState(SessionState):
    """已登录状态"""
    
    def on_message(self, context, msg):
        if msg.type == 'Logout':
            print("收到登出请求,切换到登出中状态")
            context.set_state(LoggingOutState())
        elif msg.type == 'Heartbeat':
            print("处理心跳消息")
        else:
            print(f"处理业务消息: {msg.type}")
    
    def on_timeout(self, context):
        print("心跳超时,发送心跳请求")
        context.send_heartbeat()

class SessionContext:
    """会话上下文,持有当前状态"""
    
    def __init__(self):
        self._state = LoggingInState()
    
    def set_state(self, state):
        self._state = state
    
    def handle_message(self, msg):
        self._state.on_message(self, msg)
    
    def handle_timeout(self):
        self._state.on_timeout(self)

我的经验:状态模式适合状态数量固定、状态间转换逻辑复杂的场景。但别滥用——如果只有两三个状态,用这个模式反而显得笨重。

3.2 使用字典驱动状态机

这是我最喜欢的方式。轻量、高效、一目了然。

说白了,就是用字典把「当前状态 + 事件 -> 下一个状态 + 动作」这个映射关系存起来。Python的字典天生就是干这个的。

我记得有一次做高频交易系统的协议解析,要求延迟在微秒级。状态模式那种对象创建的开销,根本扛不住。字典驱动的方式,直接查表,性能好得多。

class DictDrivenStateMachine:
    """字典驱动的状态机"""
    
    def __init__(self):
        self.state = 'INIT'
        self.transitions = {
            ('INIT', 'CONNECT'): ('CONNECTING', self._on_connect),
            ('CONNECTING', 'SUCCESS'): ('CONNECTED', self._on_connected),
            ('CONNECTING', 'FAIL'): ('INIT', self._on_connect_fail),
            ('CONNECTED', 'LOGIN'): ('LOGGING_IN', self._on_login),
            ('LOGGING_IN', 'LOGIN_OK'): ('READY', self._on_login_ok),
            ('LOGGING_IN', 'LOGIN_FAIL'): ('CONNECTED', self._on_login_fail),
            ('READY', 'HEARTBEAT'): ('READY', self._on_heartbeat),
            ('READY', 'TIMEOUT'): ('RECONNECTING', self._on_timeout),
            ('RECONNECTING', 'RECONNECT_OK'): ('CONNECTED', self._on_reconnect_ok),
            ('RECONNECTING', 'RECONNECT_FAIL'): ('DISCONNECTED', self._on_disconnect),
        }
    
    def _on_connect(self, event_data):
        print(f"发起连接: {event_data}")
    
    def _on_connected(self, event_data):
        print(f"连接成功: {event_data}")
    
    def _on_login(self, event_data):
        print(f"发送登录请求: {event_data}")
    
    def _on_login_ok(self, event_data):
        print(f"登录成功: {event_data}")
    
    def _on_heartbeat(self, event_data):
        print(f"处理心跳: {event_data}")
    
    def _on_timeout(self, event_data):
        print(f"超时处理: {event_data}")
    
    def _on_reconnect_ok(self, event_data):
        print(f"重连成功: {event_data}")
    
    def _on_disconnect(self, event_data):
        print(f"断开连接: {event_data}")
    
    def _on_connect_fail(self, event_data):
        print(f"连接失败: {event_data}")
    
    def _on_login_fail(self, event_data):
        print(f"登录失败: {event_data}")
    
    def trigger(self, event, event_data=None):
        key = (self.state, event)
        if key in self.transitions:
            next_state, action = self.transitions[key]
            action(event_data)
            self.state = next_state
            print(f"状态变更: {key[0]} -> {next_state}")
        else:
            print(f"非法转换: 当前状态={self.state}, 事件={event}")

避坑指南:我曾经在字典驱动状态机里忘了处理所有可能的转换组合,结果线上出现了「状态卡死」的情况。后来我加了一个validate_transitions()方法,在初始化时自动检查所有状态是否都有对应的处理逻辑。

3.3 第三方库transitions介绍

如果你不想自己造轮子,transitions这个库值得一试。

我第一次用这个库的时候,说实话有点惊艳。几行代码就能搭出一个完整的状态机。而且它支持分层状态、并行状态这些高级特性。

from transitions import Machine
import random

class TradingSession:
    """交易会话状态机"""
    
    states = ['disconnected', 'connecting', 'connected', 'logging_in', 
              'ready', 'reconnecting', 'disconnecting']
    
    def __init__(self):
        self.machine = Machine(
            model=self,
            states=TradingSession.states,
            initial='disconnected',
            auto_transitions=False
        )
        
        # 定义转换
        self.machine.add_transition('connect', 'disconnected', 'connecting', 
                                     after='on_connect')
        self.machine.add_transition('connect_success', 'connecting', 'connected',
                                     after='on_connected')
        self.machine.add_transition('connect_fail', 'connecting', 'disconnected',
                                     after='on_connect_fail')
        self.machine.add_transition('login', 'connected', 'logging_in',
                                     after='on_login')
        self.machine.add_transition('login_ok', 'logging_in', 'ready',
                                     after='on_login_ok')
        self.machine.add_transition('login_fail', 'logging_in', 'connected',
                                     after='on_login_fail')
        self.machine.add_transition('heartbeat', 'ready', 'ready',
                                     after='on_heartbeat')
        self.machine.add_transition('timeout', 'ready', 'reconnecting',
                                     after='on_timeout')
        self.machine.add_transition('reconnect_ok', 'reconnecting', 'connected',
                                     after='on_reconnect_ok')
        self.machine.add_transition('reconnect_fail', 'reconnecting', 'disconnected',
                                     after='on_disconnect')
        self.machine.add_transition('disconnect', '*', 'disconnecting',
                                     after='on_disconnect')
        self.machine.add_transition('disconnected', 'disconnecting', 'disconnected',
                                     after='on_disconnected')
    
    def on_connect(self):
        print("发起连接...")
    
    def on_connected(self):
        print("连接建立成功")
    
    def on_connect_fail(self):
        print("连接失败")
    
    def on_login(self):
        print("发送登录请求...")
    
    def on_login_ok(self):
        print("登录成功,准备就绪")
    
    def on_heartbeat(self):
        print("处理心跳消息")
    
    def on_timeout(self):
        print("超时,准备重连")
    
    def on_reconnect_ok(self):
        print("重连成功")
    
    def on_disconnect(self):
        print("断开连接...")
    
    def on_disconnected(self):
        print("已断开")

# 使用示例
session = TradingSession()
print(f"初始状态: {session.state}")

session.connect()
print(f"当前状态: {session.state}")

# 模拟连接成功
session.connect_success()
print(f"当前状态: {session.state}")

# 模拟登录
session.login()
session.login_ok()
print(f"当前状态: {session.state}")

# 模拟心跳
session.heartbeat()
print(f"当前状态: {session.state}")

transitions的优势:

  • 支持条件转换(只有满足条件才触发)
  • 支持转换前后的回调函数
  • 支持状态进入/离开时的钩子函数
  • 内置状态图可视化功能
  • 支持分层状态机(状态里嵌套状态机)

3.4 三种方式的对比

特性 状态模式 字典驱动 transitions
代码可读性
性能 中(有对象开销) 高(直接查表)
灵活性
学习成本
适合场景 状态少、逻辑复杂 性能敏感、状态多 快速原型、复杂状态机

3.5 我的选择建议

嗯,这里要注意。没有银弹。

我个人习惯是这样:

  • 做原型验证:直接用transitions,省时间
  • 做高频交易系统:用字典驱动,性能第一
  • 做复杂的业务逻辑:用状态模式,方便扩展和维护

我曾经在一个项目里,三种方式混着用。核心链路用字典驱动,外围逻辑用transitions,复杂的业务状态用状态模式。效果还不错。

最后说一句:状态机的实现方式不重要,重要的是你理解了「状态」和「转换」这两个核心概念。工具只是工具,脑子里的模型才是关键。

Python状态机实现方式对比 状态模式 字典驱动 transitions 每个状态一个类 行为封装在状态对象中 适合复杂业务逻辑 字典存储转换映射 直接查表,性能高 适合高频交易场景 第三方库,开箱即用 支持分层/并行状态 适合快速原型开发 核心概念:状态 + 转换 选择建议 原型验证 → transitions 高频交易 → 字典驱动 复杂业务 → 状态模式

小提示:不管你选哪种方式,先把状态转换图画出来。画图的过程,就是理清逻辑的过程。我每次做新协议解析,第一件事就是拿白板画状态图。

专注资料整理