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

实现简单FSM的Pythonic方法是什么?,python,fsm,Python,Fsm,昨天我不得不解析一个非常简单的二进制数据文件-规则是,在一行中查找两个字节,都是0xAA,然后下一个字节将是一个长度字节,然后跳过9个字节并从那里输出给定数量的数据。重复到文件末尾 我的解决方案确实有效,并且很快就组合起来了(尽管我本质上是一个C程序员,但我仍然认为用Python编写这个程序比用C编写要快得多)——但是,它显然一点也不Pythonic,而且读起来像一个C程序(在这方面不是一个很好的程序!) 对于这一点,什么是更好/更具蟒蛇风格的方法?像这样简单的FSM在Python中仍然是正确的

昨天我不得不解析一个非常简单的二进制数据文件-规则是,在一行中查找两个字节,都是0xAA,然后下一个字节将是一个长度字节,然后跳过9个字节并从那里输出给定数量的数据。重复到文件末尾

我的解决方案确实有效,并且很快就组合起来了(尽管我本质上是一个C程序员,但我仍然认为用Python编写这个程序比用C编写要快得多)——但是,它显然一点也不Pythonic,而且读起来像一个C程序(在这方面不是一个很好的程序!)

对于这一点,什么是更好/更具蟒蛇风格的方法?像这样简单的FSM在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"

我认为您的解决方案看起来不错,只是您应该将
count=count-1
替换为
count-=1


在这种情况下,花哨的代码展示会出现一种方法,通过一个小的驱动程序函数,让dict将状态映射到可调用项,但这并不是更好,只是更花哨,并且使用了更模糊的语言特性。

我见过的在Python中实现FSMs的最酷的方法是通过生成器和协同程序。看看这个例子。伊莱·本德斯基也有


如果大家对合作项目不熟悉,那么大卫·比兹利(David Beazley)的合作项目就是一个很好的介绍。

我建议大卫·默茨(David Mertz)来看看。他用Python实现了一个非常优雅的状态机类。

为了提高可读性,可以为状态指定常量名称,而不是使用0、1、2等

您可以使用字典映射
(当前\u状态,输入)->(下一个\u状态)
,但这实际上不允许您在转换期间执行任何附加处理。除非你也包含一些“转换函数”来做额外的处理

或者,您可以采用非FSM方法。我认为只要
0xAA 0xAA
仅在指示“开始”时显示(不显示在数据中),这项功能就可以工作


如果它确实出现在数据中,您可以改为使用
string.find('\xaa\xaa',start)
扫描字符串,设置
start
参数开始查找最后一个数据块的结束位置。重复,直到它返回-1。

我认为最具python风格的方式应该是像FogleBird建议的那样,但是从(当前状态,输入)映射到一个处理处理处理和转换的函数。

我有点担心告诉任何人什么是python风格,但现在开始。首先,请记住,在python中,函数只是对象。可以使用一个字典来定义转换,该字典将(输入,当前_状态)作为键,元组(下一个_状态,动作)作为值。Action只是一个函数,它执行从当前状态转换到下一状态所需的任何操作

这里有一个很好的例子。我没用过它,但从快速阅读来看,它似乎涵盖了一切


几个月前,这里提出/回答了一个类似的问题:。您可能会发现查看这些响应也很有用。

您可以使用regexp。类似这样的代码将找到第一个数据块。那么,这只是一个从上一场比赛之后开始下一次搜索的情况

find_header = re.compile('\xaa\xaa(.).{9}', re.DOTALL)
m = find_header.search(input_text)
if m:
    length = chr(find_header.group(1))
    data = input_text[m.end():m.end() + length]

我觉得不错。我就是这么写的。嗯,首先你需要一座啤酒火山,然后最好是一套海盗装备。。。等等,我们这里说的是哪个FSM?我一定累了;我一直把FSM解读为飞行的意大利面怪物。哇!谢谢——我已经开始读比兹利参考书了,它将占用整个晚上的时间,但它已经是值得的了。它是惊人的。我来自一个非常(好的,完全)紧迫的背景,当我第一次读到它时,它完全让我震惊。很有趣-谢谢!对于我的具体案例,可能有点过头了,但还是很酷。太棒了-谢谢!我知道会有类似的方法,但我不能完全理解。哦-0xAA 0xAA不出现在有效数据中。它可能会出现在有效数据块之间的垃圾中,在这种情况下,您的解决方案和我的解决方案(或AFAICT的任何其他解决方案)都无法处理它。我认为这比大多数过于面向对象的方法更具python风格。
find_header = re.compile('\xaa\xaa(.).{9}', re.DOTALL)
m = find_header.search(input_text)
if m:
    length = chr(find_header.group(1))
    data = input_text[m.end():m.end() + length]