13

昨天我不得不解析一个非常简单的二进制数据文件 - 规则是,在一行中查找两个都是 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"
4

7 回答 7

7

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

您可以使用字典来 map (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。

于 2010-05-26T19:58:23.893 回答
6

我见过在 Python 中实现 FSM 的最酷的方法是通过生成器和协程。有关示例,请参阅这篇迷人的 Python 帖子。Eli Bendersky 对这个主题也有出色的处理

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

于 2010-05-26T19:56:54.537 回答
3

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

在http://code.activestate.com/recipes/146262-finite-state-machine-fsm有一个很好看的例子。我没有使用它,但从快速阅读来看,它似乎涵盖了所有内容。

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

于 2010-05-26T20:04:49.580 回答
1

我认为您的解决方案看起来不错,除了您应该替换count = count - 1count -= 1.

这是那些花哨的代码炫耀将出现将状态映射到可调用对象的方式之一,带有一个小的驱动程序函数,但它并没有更好,只是更花哨,并且使用了更晦涩的语言功能。

于 2010-05-26T19:56:07.723 回答
1

我建议查看David Mertz 撰写的 Python 文本处理的第 4 章。他用 Python 实现了一个非常优雅的状态机类。

于 2010-05-26T19:57:16.393 回答
1

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

于 2010-05-26T20:01:05.063 回答
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]
于 2010-05-26T20:06:55.090 回答