Python état-conception d'une machine
lié à cette question de débordement de pile (C state-machine design) , pourriez-vous partager vos techniques de débordement de pile avec moi (et la communauté)?
en ce moment, je vais chercher un moteur basé sur ce qui suit:
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
mais je suis sûr qu'il y a des tonnes de façons d'y aller tout en tirant parti de la nature dynamique de Python (par exemple dynamic dispatching).
je suis après les techniques de conception pour le" moteur "qui reçoit les" événements "et les" expéditions "contre ceux basés sur l '"état" de la machine.
11 réponses
Je ne comprends pas vraiment la question. Le État modèle de conception est assez clair. Voir le livre "Design Patterns" .
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()
c'est assez courant boilerplate, utilisé en Java, C++, Python (et je suis sûr que d'autres langues, aussi).
si vos règles de transition d'état se trouvent être triviales, il y a quelques optimisations pour pousser la règle de transition elle-même dans la superclasse.
notez que nous avons besoin de références forward, nous nous référons donc aux classes par nom, et utilisons eval
pour traduire un nom de classe en classe réelle. L'alternative est de faire les variables d'instance des règles de transition au lieu des variables de classe, puis de créer les instances une fois que toutes les classes sont définies.
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" }
dans certains cas, votre événement n'est pas aussi simple que tester des objets pour l'égalité, donc une règle de transition plus générale est d'utiliser une liste appropriée de fonction-paires d'objets.
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") ]
puisque les règles sont évaluées séquentiellement, cela permet une règle" par défaut".
dans le numéro d'avril 2009 de Python Magazine, j'ai écrit un article sur l'intégration D'un État DSL dans Python, en utilisant pyparsing et imputil. Ce code vous permettrait d'écrire le module 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)
et le compilateur DSL créerait toutes les classes TrafficLight, rouge, jaune et vert nécessaires, et les méthodes de transition d'état appropriées. Le Code pourrait appeler ces classes en utilisant quelque chose comme ceci:
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()
(Malheureusement, imputil a été abandonné en Python 3.)
Il y a ce modèle de conception pour l'utilisation de décorateurs afin de mettre en œuvre des machines à états. De la description sur la page:
Lesdécorateurs sont utilisés pour spécifier les méthodes qui sont les gestionnaires d'événements pour la classe.
il y a aussi du code exemple sur la page (c'est assez long donc je ne vais pas le coller ici).
Je n'étais pas satisfait des options actuelles pour state_machines donc j'ai écrit la bibliothèque state_machine .
vous pouvez l'installer par pip install state_machine
et l'utiliser comme suit:
@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
je pense que la réponse de S. Lott est une bien meilleure façon de mettre en œuvre une machine d'état, mais si vous voulez toujours poursuivre votre approche, utiliser (state,event)
comme clé pour votre dict
est mieux. Modifier votre code:
class HandlerFsm(object):
_fsm = {
("state_a","event"): "next_state",
#...
}
cela dépend probablement de la complexité de votre machine d'état. Pour les machines d'état simples, un dict de dicts (des event-keys aux state-keys pour Dfa, ou event-keys aux listes/sets/tuples de State-keys pour NFA) sera probablement la chose la plus simple à écrire et à comprendre.
pour les machines d'état plus complexes, j'ai entendu de bonnes choses au sujet de SMC , qui peut compiler des descriptions de machines d'état déclaratives pour coder dans une grande variété de langues, y compris Python.
le code suivant est une solution très simple. La seule partie intéressante est:
def next_state(self,cls):
self.__class__ = cls
toute la logique pour chaque État est contenue dans une classe séparée. Le "state" est modifié en remplaçant le _ _ class _ _ _ " de l'instance courante.
#!/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
résultat:
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}
Je ne recommande certainement pas la mise en œuvre d'un tel modèle bien connu vous-même. Il suffit d'opter pour une implémentation open source comme transitions et envelopper une autre classe si vous avez besoin de fonctionnalités personnalisées. Dans ce post je vais expliquer pourquoi je préfère cette mise en œuvre et de ses caractéristiques.
je pense que l'outil PySCXML a besoin d'un examen plus attentif aussi.
ce projet utilise la définition du W3C: State Chart XML (SCXML) : Notation de la Machine D'État pour L'Abstraction de contrôle
SCXML fournit un environnement d'exécution Générique basé sur état-machine basé sur les Tables D'état CCXML et Harel
actuellement, SCXML est un projet de travail; mais les chances sont assez élevés qu'il obtient une recommandation W3C bientôt (il s'agit de la 9ème ébauche).
un autre point intéressant à souligner est Qu'il existe un projet Apache Commons visant à créer et à maintenir un moteur Scxml Java capable d'exécuter une machine d'état définie à l'aide d'un document SCXML, tout en faisant abstraction des interfaces d'environnement...
et pour certains autres outils, le support de cette technologie émergera à l'avenir lorsque SCXML quittera son projet de statut...
Je ne pense pas que d'atteindre une machine d'état fini pour manipuler XML. La manière habituelle pour ce faire, je pense, est d'utiliser une pile:
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)
pour chaque type d'élément, vous avez juste besoin d'une classe qui supporte les méthodes addCharacters
, addElement
, et close
.
EDIT: pour clarifier, Oui je veux dire pour faire valoir que les machines d'état finis sont généralement la mauvaise réponse, que comme une programmation À Usage général la technique, c'est des conneries et tu devrais rester à l'écart.
il y a quelques problèmes vraiment bien compris et bien définis pour lesquels les Sgsf sont une bonne solution. lex
, par exemple, est une bonne chose.
cela dit, les SGF ne s'adaptent généralement pas bien au changement. Supposons qu'un jour vous voulez ajouter un peu d'état, peut-être un "Avons-nous déjà vu l'élément X?" drapeau. Dans le code ci-dessus, vous ajoutez un attribut booléen à la classe d'élément appropriée et vous avez terminé. Dans une machine d'état fini, vous doublez le nombre d'États et de transitions.
problèmes qui exigent l'état fini au début très souvent évoluer pour exiger encore plus d'état , comme peut-être un nombre , à quel point soit votre schéma FSM est grillé, ou pire, vous l'évoluez dans une sorte de machine d'état généralisée, et à ce point vous êtes vraiment en difficulté. Plus vous allez loin, plus vos règles commencent à agir comme code-mais code dans une interprétation lente un langage que personne d'autre ne connaît, pour lequel il n'y a pas de débogueur et pas d'outils.
autres projets connexes:
vous pouvez peindre state-machine et ensuite l'utiliser dans votre code.