Что такое Pythonic для реализации простого FSM?
Вчера мне пришлось разбирать очень простой файл двоичных данных - это правило, ищите два байта в строке, которые являются как 0xAA, тогда следующий байт будет байтом длины, затем пропускает 9 байтов и выводит заданную сумму данных оттуда. Повторите в конце файла.
Мое решение действительно сработало и очень быстро собралось (хотя я программист C в глубине души, я все еще думаю, что мне было быстрее написать это на Python, чем в C) - НО, он явно не является Pythonic, и он читается как программа на C (и не очень хороший в этом!)
Какой был бы лучший/более питонический подход к этому? Является ли простой 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"
Ответы
Ответ 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.
Ответ 2
Самый классный способ, который я видел для реализации FSM в Python, должен выполняться через генераторы и сопрограммы. См. Этот Очаровательный пост Python для примера. Эли Бендерски также отличное отношение к теме.
Если coroutines не знакомы с территорией, Дэвид Безли Любопытный курс по Corouts и Concurrency - это звездное введение.
Ответ 3
Я немного опасаюсь рассказывать кому-нибудь, что такое Pythonic, но здесь. Во-первых, имейте в виду, что в функциях python есть только объекты. Переходы могут быть определены с помощью словаря, который имеет значение (input, current_state) в качестве ключа и кортеж (next_state, action) в качестве значения. Действие - это просто функция, которая делает все необходимое для перехода от текущего состояния к следующему состоянию.
Вот хороший пример этого в http://code.activestate.com/recipes/146262-finite-state-machine-fsm. Я не использовал его, но из быстрого чтения кажется, что он охватывает все.
Аналогичный вопрос был задан/ответил здесь пару месяцев назад: Python state-machine design. Возможно, вы найдете также полезные ответы.
Ответ 4
Я думаю, ваше решение выглядит отлично, за исключением того, что вы должны заменить count = count - 1
на count -= 1
.
Это один из тех моментов, когда призрачные кодовые показы будут вызывать способы определения состояний сопоставления диктофона для вызывающих, с небольшой функцией драйвера, но это не лучше, просто причудливо и использование более неясных языковых функций.
Ответ 5
Я предлагаю проверить Дэвида Мерца глава 4 обработки текста на Python. Он реализует класс конечной машины в Python, который очень изящный.
Ответ 6
Я думаю, что самый питонический способ будет, как и то, что предлагал 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]