13

Yesterday I had to parse a very simple binary data file - the rule is, look for two bytes in a row that are both 0xAA, then the next byte will be a length byte, then skip 9 bytes and output the given amount of data from there. Repeat to the end of the file.

My solution did work, and was very quick to put together (even though I am a C programmer at heart, I still think it was quicker for me to write this in Python than it would have been in C) - BUT, it is clearly not at all Pythonic and it reads like a C program (and not a very good one at that!)

What would be a better / more Pythonic approach to this? Is a simple FSM like this even still the right choice in Python?

My solution:

#! /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などを使用する代わりに、州に定数名を付けることができます。

辞書を使用してマップすることもできますが、それでは(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は、対象の優れた治療法も持っています。

コルーチンがなじみのない領域である場合、DavidBeazleyのコルーチンと並行性に関する好奇心旺盛なコースは素晴らしい入門書です。

于 2010-05-26T19:56:54.537 に答える
3

Pythonic とは何かを誰かに伝えるのは少し心配ですが、これで終わりです。まず、Python では関数は単なるオブジェクトであることに注意してください。遷移は、(input, current_state) をキーとし、タプル (next_state, action) を値とするディクショナリで定義できます。アクションは、現在の状態から次の状態に遷移するために必要なことは何でも行う機能です。

http://code.activestate.com/recipes/146262-finite-state-machine-fsmに、これを行う見栄えの良い例があります。私はそれを使用していませんが、ざっと読んだだけで、すべてをカバーしているように見えます。

同様の質問が数か月前にここで尋ねられ、回答されました: Python state-machine design。これらの回答も参考になるかもしれません。

于 2010-05-26T20:04:49.580 に答える
1

count = count - 1に置き換える必要があることを除いて、ソリューションは問題ないように見えると思いますcount -= 1

これは、小さなドライバー関数を使用して、状態を呼び出し可能オブジェクトにマッピングするdictを使用する方法が登場する時期のひとつですが、それは良くはなく、より洗練されており、よりあいまいな言語機能を使用しています。

于 2010-05-26T19:56:07.723 に答える
1

DavidMertzによるPythonでのテキスト処理の第4章を確認することをお勧めします。彼はPythonで非常にエレガントなステートマシンクラスを実装しています。

于 2010-05-26T19:57:16.393 に答える
1

FogleBird が提案したように、最も Pythonic な方法があると思いますが、(現在の状態、入力) から処理と遷移を処理する関数にマッピングします。

于 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 に答える