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

时间:2010-05-26 19:38:55

标签: python fsm

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

我的解决方案确实有效,并且非常快速地组合在一起(即使我是C语言程序员,我仍然认为用Python写这个比用C语言更快) - 但是,它显然根本不是Pythonic,而且它看起来像一个C程序(并且在那个程序上不是很好!)

对此有什么更好/更Pythonic的方法?像这样的简单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"

7 个答案:

答案 0 :(得分:7)

您可以为州提供常量名称,而不是使用0,1,2等,以提高可读性。

您可以使用字典映射(current_state, input) -> (next_state),但这并不能让您在转换过程中进行任何其他处理。除非你包含一些“过渡功能”,否则也要做额外的处理。

或者您可以采用非FSM方法。我认为只要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。

答案 1 :(得分:6)

我见过在Python中实现FSM的最酷方式必须是通过生成器和协同程序。有关示例,请参阅此Charming Python post。 Eli Bendersky也有an excellent treatment of the subject

如果协同程序不熟悉,David Beazley的A Curious Course on Coroutines and Concurrency是一个很好的介绍。

答案 2 :(得分:3)

我有点担心告诉任何人什么是Pythonic,但是这里有。首先,请记住,在python函数中只是对象。可以使用以(input,current_state)作为键并将元组(next_state,action)作为值的字典来定义转换。 Action只是一个函数,可以执行从当前状态转换到下一个状态所需的任何操作。

http://code.activestate.com/recipes/146262-finite-state-machine-fsm执行此操作有一个漂亮的示例。我没有使用它,但从快速阅读看起来它似乎涵盖了一切。

几个月前在这里提出/回答了类似的问题:Python state-machine design。您可能会发现这些响应也很有用。

答案 3 :(得分:1)

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

这是其中一个花哨的代码炫耀会出现如何将状态映射到callables的方式,具有小的驱动程序功能,但它并不是更好,只是更加漂亮,并且使用更加模糊的语言功能

答案 4 :(得分:1)

我建议大卫梅兹检查chapter 4 of Text Processing in Python。他在Python中实现了一个非常优雅的状态机类。

答案 5 :(得分:1)

我认为最像pythonic的方式就像FogleBird建议的那样,但是从(当前状态,输入)映射到可以处理处理和转换的函数。

答案 6 :(得分:1)

您可以使用正则表达式。像这样的代码会找到第一个数据块。然后,这只是在上一场比赛之后开始下一次搜索的情况。

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]