用Pythonic方式实现简单的FSM是什么?

14 投票
7 回答
15721 浏览
提问于 2025-04-15 23:12

昨天我需要解析一个非常简单的二进制数据文件。规则是:找出连续两个字节都是0xAA,然后下一个字节是长度字节,接着跳过9个字节,从那里输出指定数量的数据。一直重复这个过程直到文件结束。

我的解决方案确实有效,而且很快就写好了(虽然我本质上是个C语言程序员,但我觉得用Python写这个比用C语言快得多)——不过,这显然一点也不符合Python的风格,读起来就像是个C语言程序(而且还不是很好!)

那么,有什么更好、更符合Python风格的方法呢?像这样的简单有限状态机在Python中还是合适的吗?

我的解决方案:

#! /usr/bin/python

import sys

f = open(sys.argv[1], "rb")

state = 0

if f:
    for byte in f.read():
        a = ord(byte)       
        if state == 0:
            if a == 0xAA:
                state = 1
        elif state == 1:
            if a  == 0xAA:
                state = 2
            else: 
                state = 0
        elif state == 2:
            count = a;
            skip = 9
            state = 3
        elif state == 3:
            skip = skip -1
            if skip == 0:
                state = 4
        elif state == 4:
             print "%02x" %a
             count = count -1 
             if count == 0:
                 state = 0
                 print "\r\n"

7 个回答

3

我对告诉大家什么是“Pythonic”有点紧张,不过我还是试试吧。首先,记住在Python中,函数其实就是对象。状态转换可以用一个字典来定义,字典的键是(输入,当前状态),而值是一个元组(下一个状态,动作)。这个动作就是一个函数,它负责完成从当前状态到下一个状态的转换。

在这里有一个很不错的例子,可以看看:http://code.activestate.com/recipes/146262-finite-state-machine-fsm。我自己没有用过,但从快速浏览来看,它似乎涵盖了所有内容。

几个月前这里也有人问过类似的问题并得到了回答:Python状态机设计。你也可以看看那些回答,可能会对你有帮助。

9

我见过的在Python中实现状态机(FSM)最酷的方法就是通过生成器和协程。你可以看看这篇Charming Python的文章,里面有个例子。Eli Bendersky也写了一篇很棒的文章,讲了这个主题。

如果你对协程还不太熟悉,David Beazley的《关于协程和并发的有趣课程》是个很好的入门资料。

7

你可以给你的状态起个常量名字,而不是用0、1、2这些数字,这样会让代码更容易读懂。

你可以用一个字典来把 (当前状态, 输入) -> (下一个状态) 进行映射,但这样做其实不能在状态转换时进行额外的处理。除非你还加一个“转换函数”来处理额外的事情。

或者你可以采用一种非有限状态机的方法。我觉得这样做是可行的,只要 0xAA 0xAA 只在表示“开始”时出现(在数据中不出现)。

with open(sys.argv[1], 'rb') as f:
    contents = f.read()
    for chunk in contents.split('\xaa\xaa')[1:]:
        length = ord(chunk[0])
        data = chunk[10:10+length]
        print data

如果它在数据中出现了,你可以用 string.find('\xaa\xaa', start) 来扫描字符串,把 start 参数设置为从上一个数据块结束的地方开始查找。一直重复这个过程,直到返回-1为止。

撰写回答