【问题标题】:What is the Pythonic way to implement a simple FSM?实现简单 FSM 的 Pythonic 方式是什么?
【发布时间】:2010-05-26 19:38:55
【问题描述】:

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

【问题讨论】:

  • 对我来说看起来不错。我就是这样写的。
  • 嗯,首先你需要一座啤酒火山,然后最好是一套海盗装……等等,我们在这里说的是哪个 FSM?
  • 我一定累了;我一直把 FSM 看成会飞的意大利面怪物。

标签: python fsm


【解决方案1】:

为了提高可读性,您可以为状态命名而不是使用 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。

【讨论】:

  • 太棒了——谢谢!我知道会有这样的方式,但我无法完全理解它。
  • 哦 - 0xAA 0xAA 没有出现在有效数据中。它可能会出现在有效数据块之间的垃圾中,在这种情况下,您的解决方案和我的解决方案(或任何其他解决方案 AFAICT)都无法处理它。
  • 我认为这比大多数过于 OOP-ish 的方式更 Pythonic。 github.com/kashifrazzaqui/themstates
【解决方案2】:

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

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

【讨论】:

  • 哇!谢谢 --- 我已经开始阅读 Beazley 的参考资料,这将花费整个晚上的大部分时间,但已经值得了。
  • 这太棒了。我来自一个沉重的(好吧,完全)命令背景,我第一次读它时就完全震惊了。
  • 有趣 - 谢谢!对于我的具体情况可能有点矫枉过正,但仍然非常酷。
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

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

    这是那些花哨的代码炫耀会出现的方式之一.

    【讨论】:

      【解决方案5】:

      我建议查看 David Mertz 的 chapter 4 of Text Processing in Python。他用 Python 实现了一个非常优雅的状态机类。

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          您可以使用正则表达式。像这样的代码会找到第一个数据块。那么这只是从上一次匹配之后开始下一次搜索的情况。

          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]
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-02-09
            • 1970-01-01
            • 2011-02-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多