用Pythonic方式实现简单的FSM是什么?
昨天我需要解析一个非常简单的二进制数据文件。规则是:找出连续两个字节都是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 个回答
我对告诉大家什么是“Pythonic”有点紧张,不过我还是试试吧。首先,记住在Python中,函数其实就是对象。状态转换可以用一个字典来定义,字典的键是(输入,当前状态),而值是一个元组(下一个状态,动作)。这个动作就是一个函数,它负责完成从当前状态到下一个状态的转换。
在这里有一个很不错的例子,可以看看:http://code.activestate.com/recipes/146262-finite-state-machine-fsm。我自己没有用过,但从快速浏览来看,它似乎涵盖了所有内容。
几个月前这里也有人问过类似的问题并得到了回答:Python状态机设计。你也可以看看那些回答,可能会对你有帮助。
我见过的在Python中实现状态机(FSM)最酷的方法就是通过生成器和协程。你可以看看这篇Charming Python的文章,里面有个例子。Eli Bendersky也写了一篇很棒的文章,讲了这个主题。
如果你对协程还不太熟悉,David Beazley的《关于协程和并发的有趣课程》是个很好的入门资料。
你可以给你的状态起个常量名字,而不是用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为止。