我正在使用transitions 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)
它可以在load_data中抛出IOError
。我的问题是,在before
回调中引发异常(在本例中是隐式的还是显式的)是否安全?如果IOError
没有发生转换,则此示例的状态仍为idle
,并且未调用任何after
回调。但是,我想知道机器实例的内部状态是否会被破坏。
附加问题:是否有更好的方法可以向应用程序发出具体信息错误信号?在这个例子中,我可以使用条件加载文件,但这看起来很难看,我需要一些额外的属性来跟踪错误等。
感谢您提供任何帮助或建议。
答案 0 :(得分:2)
但是,我想知道机器实例的内部状态是否会被破坏。
除非您打算使用转换的queued
功能,否则可以在回调函数中使用异常。
按以下顺序执行转换:
prepare -> conditions -> before -> on_exit -> set_state -> on_enter -> after
如果set_state
之前的任何内容引发异常或conditions
中的某个功能未返回True
,则会暂停转换。
您的模型可能处于未定义状态。如果你依赖一些'清理'或者'拆除'在State.on_enter
或after
:
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
这将创建此状态机: