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.

42
demandé sur Peter Mortensen 2010-01-20 17:22:22

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".

38
répondu S.Lott 2010-01-20 18:38:28

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.)

11
répondu PaulMcG 2010-01-22 00:13:28

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:

Les

dé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).

8
répondu Trent 2012-12-05 01:56:41

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
5
répondu Jonathan 2018-07-04 00:26:02

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",
    #...
  }
3
répondu MAK 2010-01-20 15:01:50

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.

2
répondu Ben Karel 2010-01-20 14:45:07

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}
2
répondu cmcginty 2011-05-28 01:24:34

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.

2
répondu Iwan LD 2017-05-23 10:30:59

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...

2
répondu gecco 2018-07-04 00:28:24

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.

1
répondu Jason Orendorff 2010-01-20 16:00:46

autres projets connexes:

vous pouvez peindre state-machine et ensuite l'utiliser dans votre code.

0
répondu Veselin Penev 2018-07-04 00:27:07