1

我正在使用转换FSM 库。想象一下使用以下代码有一个应用程序 FSM:

from transitions import Machine

import os

class Application(object):

  states = ["idle", "data_loaded"]

  def __init__(self):
    self.data = None
    machine = Machine(model=self, states=Application.states, initial="idle")
    machine.add_transition("filename_dropped",
                           source="idle",
                           dest="data_loaded",
                           before="load_data",
                           conditions="is_valid_filename")
    self.machine = machine

  def drop_filename(self, filename):
    try:
      self.filename_dropped(filename)
    except IOError as exc:
      print "Oops: %s" % str(exc)

  def load_data(self, filename):
    with open(filename) as file:
      self.data = file.read()

  def is_valid_filename(self, filename):
    return os.path.isfile(filename)

它可以IOError在 load_data 内抛出一个。before我的问题是从回调中引发异常(在本例中隐式或显式)是否安全?如果IOError没有发生转换,则此示例的状态保持不变,并且不会调用idle任何回调。after但是,我想知道机器实例的内部状态是否会损坏。

附加问题:有没有更好的方法向应用程序发出带有具体信息的错误信号?在这个例子中,我可以使用条件来加载文件,但这看起来很难看,我需要一些额外的属性来跟踪错误等。

感谢您的任何帮助或建议。

4

1 回答 1

3

但是,我想知道机器实例的内部状态是否会损坏。

除非您打算使用queued转换功能,否则可以在回调函数中引发异常。

转换按以下顺序执行:

prepare -> conditions -> before -> on_exit -> set_state -> on_enter -> after

如果之前set_state的任何东西引发 Exception 或 in 中的函数conditions没有返回True,则转换将停止。

不过,您的模型可能处于未定义状态。如果您在 or 中依赖一些“清理”或“拆除State.on_enterafter

from transitions import Machine
class Model:
    def __init__(self):
        self.busy = False
    def before(self):
        self.busy = True
        raise Exception('oops')
    def after(self):
        # if state transition is done, reset busy
        self.busy = False

model = Model()
m = Machine(model, states=['A','B'], initial='A',
            transitions=[{'trigger':'go', 'source':'A', 'dest':'B',
                          'before':'before', 'after':'after'}])
try:
  model.go()
except Exception as e:
  print "Exception: %s" % e # Exception: oops
print "State: %s" % model.state # State: A
print "Model busy: %r" % model.busy # Model busy: True

有没有更好的方法向应用程序发出带有具体信息的错误信号?

这取决于您想要达到的目标。引发错误/异常通常会停止当前任务的执行。在我看来,这几乎是传播问题的方式。如果你想处理错误并将错误表示为一种状态,我不会考虑使用conditions丑陋的。具有相同的有效转换trigger按照它们添加的顺序进行评估。考虑到这一点,并且使用unless否定表示法conditions,您的代码可能如下所示:

from transitions import Machine
import os


class Application(object):
  states = ["idle", "data_loaded", "filename_invalid", "data_invalid"]

  transitions = [
    {'trigger': 'filename_dropped', 'source': 'idle', 
     'dest': 'filename_invalid', 'unless': 'is_valid_filename'},
    {'trigger':'filename_dropped', 'source': 'idle',
     'dest':'data_invalid', 'unless': 'is_valid_data'},
    {'trigger':'filename_dropped', 'source': 'idle',
     'dest':'data_loaded'}
  ]

  def __init__(self):
    self.data = None
    machine = Machine(model=self, states=Application.states,
                      transitions=Application.transitions, initial="idle")
    self.machine = machine

  def drop_filename(self, filename):
      self.filename_dropped(filename)
      if self.is_data_loaded():
          print "Data loaded"

  # renamed load_data
  def is_valid_data(self, filename):
    try:
      with open(filename) as file:
        self.data = file.read()
    except IOError as exc:
      print "File loading error: %s" % str(exc)
      return False
    return True

  def is_valid_filename(self, filename):
    return os.path.isfile(filename)

app = Application()
app.drop_filename('test.txt')
# >>> Data loaded
print app.state
# >>> data_loaded
app.to_idle()
app.drop_filename('missing.txt')
print app.state
# >>> filename_invalid
app.to_idle()
app.drop_filename('secret.txt')
# >>> File loading error: [Errno 13] Permission denied: 'secret.txt'
print app.state
# >>> data_invalid

这将创建这个状态机:

在此处输入图像描述

于 2016-09-07T12:15:02.260 回答