Python state-дизайн машины
относится к этот вопрос переполнения стека (c state-machine design), не могли бы вы Stack Overflow люди поделиться своими методами проектирования Python state-machine со мной (и сообществом)?
на данный момент я собираюсь для двигателя, основанного на следующем:
class TrackInfoHandler(object):
def __init__(self):
self._state="begin"
self._acc=""
## ================================== Event callbacks
def startElement(self, name, attrs):
self._dispatch(("startElement", name, attrs))
def characters(self, ch):
self._acc+=ch
def endElement(self, name):
self._dispatch(("endElement", self._acc))
self._acc=""
## ===================================
def _missingState(self, _event):
raise HandlerException("missing state(%s)" % self._state)
def _dispatch(self, event):
methodName="st_"+self._state
getattr(self, methodName, self._missingState)(event)
## =================================== State related callbacks
но я уверен, что есть множество способов сделать это, используя динамическую природу Python (например, динамическую диспетчеризацию).
Я после методов дизайна для "двигатель", который получает" события "и" отправляет "против тех, которые основаны на" состоянии " машины.
11 ответов
Я действительно не понимаю вопроса. The государство шаблон дизайна довольно ясен. Вижу шаблоны проектирования книги.
class SuperState( object ):
def someStatefulMethod( self ):
raise NotImplementedError()
def transitionRule( self, input ):
raise NotImplementedError()
class SomeState( SuperState ):
def someStatefulMethod( self ):
actually do something()
def transitionRule( self, input ):
return NextState()
Это довольно распространенный шаблон, используемый в Java, C++, Python (и я уверен, что и другие языки).
если ваши правила перехода состояния оказываются тривиальными, есть некоторые оптимизации, чтобы подтолкнуть само правило перехода в суперкласс.
обратите внимание, что нам нужно иметь прямые ссылки, поэтому мы обратитесь к классам по имени и используйте eval
чтобы перевести имя класса в фактический класс. Альтернативой является создание переменных экземпляра правил перехода вместо переменных класса, а затем создание экземпляров после определения всех классов.
class State( object ):
def transitionRule( self, input ):
return eval(self.map[input])()
class S1( State ):
map = { "input": "S2", "other": "S3" }
pass # Overrides to state-specific methods
class S2( State ):
map = { "foo": "S1", "bar": "S2" }
class S3( State ):
map = { "quux": "S1" }
в некоторых случаях ваше событие не так просто, как тестирование объектов на равенство, поэтому более общее правило перехода-использовать правильный список пар функция-объект.
class State( object ):
def transitionRule( self, input ):
next_states = [ s for f,s in self.map if f(input) ]
assert len(next_states) >= 1, "faulty transition rule"
return eval(next_states[0])()
class S1( State ):
map = [ (lambda x: x == "input", "S2"), (lambda x: x == "other", "S3" ) ]
class S2( State ):
map = [ (lambda x: "bar" <= x <= "foo", "S3"), (lambda x: True, "S1") ]
поскольку правила оцениваются последовательно это позволяет правило" по умолчанию".
в апреле 2009 года в журнале Python я написал статью о внедрении состояния DSL в Python, используя pyparsing и imputil. Этот код позволит вам написать модуль trafficLight.pystate:
# trafficLight.pystate
# define state machine
statemachine TrafficLight:
Red -> Green
Green -> Yellow
Yellow -> Red
# define some class level constants
Red.carsCanGo = False
Yellow.carsCanGo = True
Green.carsCanGo = True
Red.delay = wait(20)
Yellow.delay = wait(3)
Green.delay = wait(15)
и компилятор DSL создаст все необходимые классы TrafficLight, Red, Yellow и Green и правильные методы перехода состояния. Код может вызывать эти классы, используя что-то вроде этого:
import statemachine
import trafficLight
tl = trafficLight.Red()
for i in range(6):
print tl, "GO" if tl.carsCanGo else "STOP"
tl.delay()
tl = tl.next_state()
(к сожалению, imputil был упал в Python 3.)
здесь этот шаблон для использования декораторов для реализации государственных машин. Из описания на странице:
декораторы используются для указания, какие методы обработчики событий для класса.
на странице также есть пример кода (он довольно длинный, поэтому я не буду вставлять его здесь).
Я также не был доволен текущими параметрами state_machines, поэтому я написал state_machine библиотека.
Вы можете установить его с помощью pip install state_machine
и используйте его так:
@acts_as_state_machine
class Person():
name = 'Billy'
sleeping = State(initial=True)
running = State()
cleaning = State()
run = Event(from_states=sleeping, to_state=running)
cleanup = Event(from_states=running, to_state=cleaning)
sleep = Event(from_states=(running, cleaning), to_state=sleeping)
@before('sleep')
def do_one_thing(self):
print "{} is sleepy".format(self.name)
@before('sleep')
def do_another_thing(self):
print "{} is REALLY sleepy".format(self.name)
@after('sleep')
def snore(self):
print "Zzzzzzzzzzzz"
@after('sleep')
def big_snore(self):
print "Zzzzzzzzzzzzzzzzzzzzzz"
person = Person()
print person.current_state == person.sleeping # True
print person.is_sleeping # True
print person.is_running # False
person.run()
print person.is_running # True
person.sleep()
# Billy is sleepy
# Billy is REALLY sleepy
# Zzzzzzzzzzzz
# Zzzzzzzzzzzzzzzzzzzzzz
print person.is_sleeping # True
Я думаю, что ответ С. Лотта-гораздо лучший способ реализовать государственную машину, но если вы все еще хотите продолжить свой подход, используя (state,event)
как ключ для вашего dict
лучше. Изменение кода:
class HandlerFsm(object):
_fsm = {
("state_a","event"): "next_state",
#...
}
Это, наверное, зависит от того, насколько сложна ваша государственная машина. Для простых машин состояний дикт диктов (ключей событий к ключам состояний для DFAs или ключей событий к спискам/наборам/кортежам ключей состояний для NFAs), вероятно, будет самой простой вещью для записи и понимания.
для более сложных государственных машин я слышал хорошие вещи о SMC, который может компилировать описания машины декларативного состояния в код на самых разных языках, включая Python.
следующий код является очень простым решением. Единственная интересная часть:
def next_state(self,cls):
self.__class__ = cls
вся логика для каждого государства содержится в отдельном классе. "Состояние" изменяется путем замены "__класс__' запущенного экземпляра.
#!/usr/bin/env python
class State(object):
call = 0 # shared state variable
def next_state(self,cls):
print '-> %s' % (cls.__name__,),
self.__class__ = cls
def show_state(self,i):
print '%2d:%2d:%s' % (self.call,i,self.__class__.__name__),
class State1(State):
__call = 0 # state variable
def __call__(self,ok):
self.show_state(self.__call)
self.call += 1
self.__call += 1
# transition
if ok: self.next_state(State2)
print '' # force new line
class State2(State):
__call = 0
def __call__(self,ok):
self.show_state(self.__call)
self.call += 1
self.__call += 1
# transition
if ok: self.next_state(State3)
else: self.next_state(State1)
print '' # force new line
class State3(State):
__call = 0
def __call__(self,ok):
self.show_state(self.__call)
self.call += 1
self.__call += 1
# transition
if not ok: self.next_state(State2)
print '' # force new line
if __name__ == '__main__':
sm = State1()
for v in [1,1,1,0,0,0,1,1,0,1,1,0,0,1,0,0,1,0,0]:
sm(v)
print '---------'
print vars(sm
результат:
0: 0:State1 -> State2
1: 0:State2 -> State3
2: 0:State3
3: 1:State3 -> State2
4: 1:State2 -> State1
5: 1:State1
6: 2:State1 -> State2
7: 2:State2 -> State3
8: 2:State3 -> State2
9: 3:State2 -> State3
10: 3:State3
11: 4:State3 -> State2
12: 4:State2 -> State1
13: 3:State1 -> State2
14: 5:State2 -> State1
15: 4:State1
16: 5:State1 -> State2
17: 6:State2 -> State1
18: 6:State1
---------
{'_State1__call': 7, 'call': 19, '_State3__call': 5, '_State2__call': 7}
Я бы определенно не рекомендовал реализовывать такой хорошо известный шаблон самостоятельно. Просто перейдите к реализации с открытым исходным кодом, например переход и оберните вокруг него другой класс, если вам нужны пользовательские функции. В этот пост Я объясняю, почему я предпочитаю эту конкретную реализацию и ее функции.
Я думаю, что инструмент PySCXML нуждается в более близком взгляде.
в этом проекте используется определение W3C:диаграмма состояния XML (SCXML): нотация государственной машины для абстракции управления
SCXML предоставляет универсальную среду выполнения на основе состояния компьютера на основе таблиц состояний CCXML и Harel
В настоящее время SCXML является рабочим проектом; но шансы довольно высоки, что он скоро получит рекомендацию W3C (это 9-й проект).
еще один интересный момент, чтобы подчеркнуть, что есть проект Apache Commons, направленный на создание и поддержание движка Java SCXML, способного выполнять государственную машину, определенную с помощью документа SCXML, абстрагируя интерфейсы среды...
и для некоторых других инструментов поддержка этой технологии появится в будущем, когда SCXML покинет свой статус проекта...
Я бы не подумал, чтобы достичь конечной машины для обработки XML. Обычный способ сделать это, я думаю, заключается в использовании стека:
class TrackInfoHandler(object):
def __init__(self):
self._stack=[]
## ================================== Event callbacks
def startElement(self, name, attrs):
cls = self.elementClasses[name]
self._stack.append(cls(**attrs))
def characters(self, ch):
self._stack[-1].addCharacters(ch)
def endElement(self, name):
e = self._stack.pop()
e.close()
if self._stack:
self._stack[-1].addElement(e)
для каждого вида элемента, вам просто нужен класс, который поддерживает addCharacters
, addElement
и close
методы.
EDIT: чтобы уточнить, да, я имею в виду, что конечные государственные машины обычно являются неправильным ответом, что в качестве метода программирования общего назначения они мусор, и вы должны остаться прочь.
есть несколько действительно хорошо понятых, четко очерченных проблем, для которых FSMs является хорошим решением. lex
, например, это хороший материал.
тем не менее, FSMs обычно не справляются с изменениями. Предположим, когда-нибудь вы захотите добавить немного состояния, например: "мы уже видели элемент X?" флаг. В приведенном выше коде вы добавляете логический атрибут к соответствующему классу элементов, и все готово. В конечной машине вы удваиваете количество состояний и переходы.
проблемы, которые требуют конечного состояния сначала очень часто развиваются, чтобы требовать еще большего состояния, например,, в этот момент либо ваша схема FSM поджаривается, либо, что еще хуже, вы превращаете ее в какую-то обобщенную государственную машину, и в этот момент вы действительно в беде. Чем дальше вы идете, тем больше ваши правила начинают действовать как код-но код на медленно интерпретируемом языке, который вы изобрели, что никто не знает, для которого нет отладчика и нет инструменты.
другие родственные проекты:
вы можете нарисовать государственную машину, а затем использовать ее в своем коде.