Python государственный дизайн машины

В связи с этим вопросом о переполнении стека (проектирование машины состояний C), не могли бы вы, ребята из Stack Overflow, поделиться своими методами проектирования машины состояний Python со мной (и сообществом)?

На данный момент я собираюсь на двигатель, основанный на следующем:

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 (например, динамическая диспетчеризация).

Я занимаюсь разработкой методов для "двигателя", который получает "события" и "рассылки" против тех, которые основаны на "состоянии" машины.

Ответ 1

Я действительно не понимаю вопрос. Шаблон State Design довольно понятен. Смотрите книгу "Шаблоны проектирования".

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") ]

Поскольку правила оцениваются последовательно, это позволяет использовать правило "по умолчанию".

Ответ 2

В выпуске журнала Python за апрель 2009 года я написал статью о внедрении 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.)

Ответ 3

Существует этот шаблон дизайна для использования декораторов для реализации государственных машин. Из описания на странице:

Декораторы используются, чтобы указать, какие методы являются обработчиками событий для класса.

Также есть пример кода на странице (он довольно длинный, поэтому я не буду вставлять его здесь).

Ответ 4

Я также не был доволен текущими опциями для 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

Ответ 5

Я думаю, что S. Lott ответ - гораздо лучший способ реализовать конечный автомат, но если вы все еще хотите продолжить свой подход, используйте (state,event), так как ключ для вашего dict лучше. Изменение кода:

class HandlerFsm(object):

  _fsm = {
    ("state_a","event"): "next_state",
    #...
  }

Ответ 6

Вероятно, это зависит от того, насколько сложна ваша машина состояния. Для простых состояний машины, диктофон dicts (ключей событий для ключей состояния для DFA или ключей событий для списков/наборов/наборов ключей состояний для NFA), вероятно, будет самой простой записью и пониманием.

Для более сложных государственных машин я слышал хорошие вещи о SMC, которые могут компилировать декларативные описания конечных автоматов для кода в широкий спектр языков, включая Python.

Ответ 7

Следующий код - действительно простое решение. Единственная интересная часть:

   def next_state(self,cls):
      self.__class__ = cls

Вся логика для каждого состояния содержится в отдельном классе. "Состояние" изменяется путем замены " __class__" исполняемого экземпляра".

#!/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}

Ответ 8

Я думаю, что инструмент PySCXML тоже нуждается в более внимательном рассмотрении.

В этом проекте используется определение W3C: XML-диаграмма состояний (SCXML): нотация конечного автомата для абстракции управления

SCXML предоставляет общую среду исполнения на основе конечного автомата, основанную на таблицах состояний CCXML и Harel

В настоящее время SCXML является рабочим проектом; но шансы довольно высоки, что он скоро получит рекомендацию W3C (это 9-й проект).

Еще один интересный момент, на который стоит обратить внимание, - это проект Apache Commons, направленный на создание и поддержку механизма Java SCXML, способного выполнять конечный автомат, определенный с помощью документа SCXML, в то же время абстрагируя интерфейсы среды...

И для некоторых других инструментов поддержка этой технологии появится в будущем, когда SCXML покидает свой черновой статус...

Ответ 9

Я бы определенно не рекомендовал реализовать такой хорошо известный шаблон самостоятельно. Просто перейдите к реализации с открытым исходным кодом, например transitions и оберните еще один класс вокруг него, если вам нужны пользовательские функции. В этом сообщении я объясню, почему я предпочитаю эту конкретную реализацию и ее функции.

Ответ 10

Я бы не подумал добраться до конечного конечного автомата для обработки 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.

РЕДАКТИРОВАТЬ: Чтобы уточнить, да, я имею в виду утверждать, что конечные машины обычно являются неправильным ответом, что в качестве метода программирования общего назначения это мусор, и вам следует избегать.

Есть несколько действительно хорошо понятых, четко очерченных проблем, для которых FSM - прекрасное решение. lex, например, хороший материал.

Тем не менее, FSM обычно не справляются с изменениями. Предположим, что когда-нибудь вы захотите добавить немного состояния, возможно, "мы еще видели элемент X?" флаг. В приведенном выше коде вы добавляете атрибут boolean в соответствующий класс элемента, и все готово. В машине с конечным состоянием вы удваиваете количество состояний и переходов.

Проблемы, требующие конечного состояния, вначале очень часто развиваются, чтобы требовать еще большего состояния, например, числа, в котором или ваша схема FSM тоста, или, что еще хуже, вы превращаете ее в какой-то обобщенный конечный автомат и на это указывает на то, что у вас действительно проблемы. Чем дальше вы идете, тем больше ваши правила начинают действовать как код, но код на медленном языке, который вы изобрели, которого никто не знает, для которого нет отладчика и никаких инструментов.

Ответ 13

Связанный проект marmoolak. Вот описание об этом.

Ответ 14

Вот решение для "statefull объектов", которое я придумал, но оно довольно неэффективно для вашей предполагаемой цели, потому что изменения состояния относительно дороги. Однако это может хорошо работать для объектов, которые изменяют состояние нечасто или подвергаются лишь ограниченному числу изменений состояния. Преимущество заключается в том, что после изменения состояния избыточная косвенность отсутствует.

class T:
    """
    Descendant of 'object' that rectifies '__new__' overriding.

    This class is intended to be listed as the last base class (just
    before the implicit 'object').  It is a part of a workaround for

      * https://bugs.python.org/issue36827
    """

    @staticmethod
    def __new__(cls, *_args, **_kwargs):
        return object.__new__(cls)

class Stateful:
    """
    Abstract base class (or mixin) for "stateful" classes.

    Subclasses must implement 'InitState' mixin.
    """

    @staticmethod
    def __new__(cls, *args, **kwargs):
        # XXX: see https://stackoverflow.com/a/9639512
        class CurrentStateProxy(cls.InitState):
            @staticmethod
            def _set_state(state_cls=cls.InitState):
                __class__.__bases__ = (state_cls,)

        class Eigenclass(CurrentStateProxy, cls):
            __new__ = None  # just in case

        return super(__class__, cls).__new__(Eigenclass, *args, **kwargs)

# XXX: see https://bugs.python.org/issue36827 for the reason for 'T'.
class StatefulThing(Stateful, T):
    class StateA:
        """First state mixin."""

        def say_hello(self):
            self._say("Hello!")
            self.hello_count += 1
            self._set_state(self.StateB)
            return True

        def say_goodbye(self):
            self._say("Another goodbye?")
            return False

    class StateB:
        """Second state mixin."""

        def say_hello(self):
            self._say("Another hello?")
            return False

        def say_goodbye(self):
            self._say("Goodbye!")
            self.goodbye_count += 1
            self._set_state(self.StateA)
            return True

    # This one is required by 'Stateful'.
    class InitState(StateA):
        """Third state mixin -- the initial state."""

        def say_goodbye(self):
            self._say("Why?")
            return False

    def __init__(self, name):
        self.name = name
        self.hello_count = self.goodbye_count = 0

    def _say(self, message):
        print("{}: {}".format(self.name, message))

    def say_hello_followed_by_goodbye(self):
        self.say_hello() and self.say_goodbye()

# ----------
# ## Demo ##
# ----------
if __name__ == "__main__":
    t1 = StatefulThing("t1")
    t2 = StatefulThing("t2")
    print("> t1, say hello.")
    t1.say_hello()
    print("> t2, say goodbye.")
    t2.say_goodbye()
    print("> t2, say hello.")
    t2.say_hello()
    print("> t1, say hello.")
    t1.say_hello()
    print("> t1, say hello followed by goodbye.")
    t1.say_hello_followed_by_goodbye()
    print("> t2, say goodbye.")
    t2.say_goodbye()
    print("> t2, say hello followed by goodbye.")
    t2.say_hello_followed_by_goodbye()
    print("> t1, say goodbye.")
    t1.say_goodbye()
    print("> t2, say hello.")
    t2.say_hello()
    print("---")
    print( "t1 said {} hellos and {} goodbyes."
           .format(t1.hello_count, t1.goodbye_count) )
    print( "t2 said {} hellos and {} goodbyes."
           .format(t2.hello_count, t2.goodbye_count) )

    # Expected output:
    #
    #     > t1, say hello.
    #     t1: Hello!
    #     > t2, say goodbye.
    #     t2: Why?
    #     > t2, say hello.
    #     t2: Hello!
    #     > t1, say hello.
    #     t1: Another hello?
    #     > t1, say hello followed by goodbye.
    #     t1: Another hello?
    #     > t2, say goodbye.
    #     t2: Goodbye!
    #     > t2, say hello followed by goodbye.
    #     t2: Hello!
    #     t2: Goodbye!
    #     > t1, say goodbye.
    #     t1: Goodbye!
    #     > t2, say hello.
    #     t2: Hello!
    #     ---
    #     t1 said 1 hellos and 1 goodbyes.
    #     t2 said 3 hellos and 2 goodbyes.

Я разместил "запрос на замечания" здесь.