Diseño de la máquina de estado de Python
state-machines fsm (11)
Creo que la herramienta PySCXML necesita una mirada más cercana.
Este proyecto utiliza la definición de W3C: State Chart XML (SCXML) : Notación de máquina de estado para abstracción de control
SCXML proporciona un entorno de ejecución basado en máquina de estado genérico basado en CCXML y Harel State Tables
Actualmente, SCXML es un borrador de trabajo; pero es muy probable que reciba una recomendación del W3C pronto (es el noveno borrador).
Otro punto interesante a destacar es que hay un proyecto de Apache Commons destinado a crear y mantener un motor Java SCXML capaz de ejecutar una máquina de estados definida usando un documento SCXML, mientras se abstraen las interfaces del entorno ...
Y para algunas otras herramientas, el soporte de esta tecnología surgirá en el futuro cuando SCXML deje su estado de borrador ...
En relación con esta pregunta sobre el desbordamiento de pila (diseño de la máquina en el estado C) , ¿podrían ustedes las personas con desbordamiento de pila compartir sus técnicas de diseño de la máquina en el estado de Python conmigo (y con la comunidad)?
En este momento, voy por un motor basado en lo siguiente:
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
Pero estoy seguro de que hay muchas formas de hacerlo mientras se aprovecha la naturaleza dinámica de Python (por ejemplo, el envío dinámico).
Estoy buscando técnicas de diseño para el "motor" que recibe los "eventos" y los "despachos" contra aquellos basados en el "estado" de la máquina.
Creo que la respuesta de S. Lott es una forma mucho mejor de implementar una máquina de estados, pero si aún desea continuar con su enfoque, es mejor usar (state,event)
como clave para su dict
. Modificando su código:
class HandlerFsm(object):
_fsm = {
("state_a","event"): "next_state",
#...
}
Definitivamente no recomendaría implementar un patrón tan conocido usted mismo. Simplemente vaya a una implementación de código abierto como transitions y envuelva otra clase a su alrededor si necesita características personalizadas. En este post explico por qué prefiero esta implementación particular y sus características.
El siguiente código es una solución realmente simple. La única parte interesante es:
def next_state(self,cls):
self.__class__ = cls
Toda la lógica para cada estado está contenida en una clase separada. El ''estado'' se cambia al reemplazar el '' __class__ '' de la instancia en ejecución.
#!/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
Resultado:
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}
En la edición de abril de 2009 de Python Magazine, escribí un artículo sobre cómo incrustar un DSL de estado en Python, usando pyparsing e imputil. Este código le permitiría escribir el módulo 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)
y el compilador DSL crearía todas las clases necesarias de TrafficLight, Red, Yellow y Green, y los métodos de transición de estado adecuados. El código podría llamar a estas clases usando algo como esto:
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()
(Desafortunadamente, el imputil se ha caído en Python 3).
Hay un patrón de diseño para usar decoradores para implementar máquinas de estado. De la descripción en la página:
Los decoradores se utilizan para especificar qué métodos son los controladores de eventos para la clase.
También hay un código de ejemplo en la página (es bastante largo, así que no lo pegaré aquí).
No pensaría buscar una máquina de estados finitos para manejar XML. La forma habitual de hacer esto, creo, es usar una pila:
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)
Para cada tipo de elemento, solo necesita una clase que admita los addCharacters
, addElement
y close
.
EDITAR: Para aclarar, sí quiero decir que las máquinas de estados finitos suelen ser la respuesta equivocada, que como una técnica de programación de propósito general son basura y usted debe mantenerse alejado.
Hay algunos problemas realmente bien entendidos y bien delineados para los cuales las FSM son una buena solución. lex
, por ejemplo, es algo bueno.
Dicho esto, los FSM normalmente no se enfrentan bien al cambio. Supongamos que algún día usted quiera agregar un poco de estado, tal vez un "¿ya hemos visto el elemento X?" bandera. En el código anterior, agrega un atributo booleano a la clase de elemento adecuada y listo. En una máquina de estados finitos, duplicas el número de estados y transiciones.
Los problemas que requieren un estado finito al principio a menudo evolucionan para requerir incluso más estado, como tal vez un número , en cuyo punto el esquema de FSM está tostado o, peor aún, evoluciona hacia algún tipo de máquina de estado generalizada, y en ese punto estamos realmente en problemas A medida que avanzas, más tus reglas comienzan a actuar como código, pero codifica en un lenguaje de interpretación lenta que inventaste para que nadie más lo sepa, para lo cual no hay depurador ni herramientas.
Otros proyectos relacionados:
Puedes pintar la máquina de estado y luego usarla en tu código.
Probablemente depende de cuán compleja sea su máquina de estados. Para las máquinas de estado simples, un dictado de dictados (de claves de evento a claves de estado para DFA, o claves de evento a listas / conjuntos / tuplas de claves de estado para NFA) probablemente sea lo más simple de escribir y entender.
Para las máquinas de estados más complejas, he escuchado cosas buenas sobre SMC , que puede compilar descripciones de máquinas de estados declarativas para codificar en una amplia variedad de idiomas, incluido Python.
Realmente no entiendo la pregunta. El patrón de diseño del estado es bastante claro. Ver el libro de Patrones de Diseño .
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()
Eso es bastante común, usado en Java, C ++, Python (y estoy seguro de que también en otros idiomas).
Si las reglas de transición de su estado resultan ser triviales, hay algunas optimizaciones para empujar la regla de transición a la superclase.
Tenga en cuenta que debemos tener referencias hacia adelante, por lo que nos referimos a las clases por su nombre y usamos eval
para traducir un nombre de clase a una clase real. La alternativa es hacer que las variables de instancia de las reglas de transición en lugar de las variables de clase y luego crear las instancias después de que todas las clases estén definidas.
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" }
En algunos casos, su evento no es tan simple como probar la igualdad de los objetos, por lo que una regla de transición más general es usar una lista adecuada de pares función-objeto.
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") ]
Dado que las reglas se evalúan secuencialmente, esto permite una regla "predeterminada".
Tampoco estaba contento con las opciones actuales para state_machines, así que escribí la biblioteca state_machine .
Puede instalarlo por pip install state_machine
y usarlo así:
@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