昨天我不得不解析一个非常简单的二进制数据文件 - 规则是,连续查找两个字节都是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"
答案 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]