设置和取消分层状态机的预定义超时失败(参考:https://github.com/tyarkoni/transitions/issues/198)

Setting and canceling of predefined timeouts for Hierarchical State Machine fails (ref: https://github.com/tyarkoni/transitions/issues/198)

我终于在尝试实现 https://github.com/tyarkoni/transitions/issues/198 中显示的超时机制。我的目标是在进入状态时设置默认超时(作为 TimeoutState 的构造函数参数)并在退出时取消它。退出状态时发生错误:

Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/IPython/core/interactiveshell.py", line 2481, in safe_execfile
self.compile if kw['shell_futures'] else None)
File "/usr/local/lib/python2.7/dist-packages/IPython/utils/py3compat.py", line 289, in execfile
builtin_mod.execfile(filename, *where)
File "/home/sge/workspace_neon/VS_AREA51/ocpp16j_experimental/hierasms.py", line 143, in <module>
collector.model.collect(a='acall1')  # collecting
File "/home/sge/workspace_neon/VS_AREA51/ocpp16j_experimental/plugged/transitions/core.py", line 248, in trigger
return self.machine._process(f)
File "/home/sge/workspace_neon/VS_AREA51/ocpp16j_experimental/plugged/transitions/core.py", line 590, in _process
return trigger()
File "/home/sge/workspace_neon/VS_AREA51/ocpp16j_experimental/plugged/transitions/extensions/nesting.py", line 131, in _trigger
if t.execute(event):
File "/home/sge/workspace_neon/VS_AREA51/ocpp16j_experimental/plugged/transitions/extensions/nesting.py", line 100, in execute
return super(NestedTransition, self).execute(event_data)
File "/home/sge/workspace_neon/VS_AREA51/ocpp16j_experimental/plugged/transitions/core.py", line 174, in execute
self._change_state(event_data)
File "/home/sge/workspace_neon/VS_AREA51/ocpp16j_experimental/plugged/transitions/extensions/nesting.py", line 108, in _change_state
lvl = source_state.exit_nested(event_data, dest_state)
File "/home/sge/workspace_neon/VS_AREA51/ocpp16j_experimental/plugged/transitions/extensions/nesting.py", line 81, in exit_nested
tmp_self.exit(event_data)
TypeError: exit() takes exactly 1 argument (2 given)

这里是代码(Python 2.7.6/IPython 5.1.0/transitions 0.4.2):

from threading import Thread, Event
from transitions.extensions import HierarchicalMachine as Machine
from transitions.extensions.nesting import NestedState as State
# Set up logging
import logging
from transitions import logger
logger.setLevel(logging.DEBUG)


class Timeout(Thread):

    def __init__(self, func, timeout, **kwargs):
#         self.lgtout=logging.getLogger(".".join([__name__, self.__class__.__name__]))
        super(Timeout, self).__init__()
        self.func = func
        self.kwargs = kwargs
        self.cancelEvt=Event()
        self.cancelEvt.clear() #make ready for delay
        self.timeout = timeout
        print '---->Starting countdown from: '+str(self.timeout)
        self.start()


    def run(self):
        self.cancelEvt.wait(self.timeout) 
        if not self.cancelEvt.isSet():
            print '---->Timeout of ' +str(self.timeout)+ ' occurred in thread'+str(self.ident)
            self.func(**self.kwargs) # trigger the sm
        else: 
            print '---->Timeout of ' +str(self.timeout)+ ' canceled in thread'+str(self.ident)


class TimeoutState(State):

    def __init__(self, name,  timeout=3, timeoutSmTrigger='timeOUT', *args, **kwargs):
        self.timeout = timeout 
        self._timer=None
        self.timeoutSmTrigger=timeoutSmTrigger
        super(TimeoutState, self).__init__(name=name, *args, **kwargs)
        print 'timeout state created: ',name


    def enter(self, **kwargs):
        # initialise timeout
        print 'enter: '+str(kwargs)
        timeout = kwargs.pop('timeout',self.timeout)
        timeoutSmTrigger = kwargs.pop('timeoutSmTrigger',self.timeoutSmTrigger)
        func = getattr(kwargs.get('model',None), timeoutSmTrigger)
        self._timer = Timeout(func, timeout, **kwargs)


    def exit(self, **kwargs):
        print 'enter: '+str(kwargs)
        if self._timer:
            self._timer.cancelEvt.set()


class TimeoutMachine(Machine):

    def __init__(self, name, states, transitions, initial, model=None):
        super(TimeoutMachine,self).__init__(model=model, states=states, initial=initial, transitions=transitions, 
                                     send_event=False, 
                                     auto_transitions=False, ordered_transitions=False, 
                                     ignore_invalid_triggers=False, name=name, queued=False)



    def _create_state(self, *args, **kwargs):
        '''
        Overwrite to create all the way states with timeout mechanism
        '''
        return TimeoutState(*args, **kwargs)       


class CntModel(object):

    def bcall(self,**kwargs):
        print 'before increase: ',str(kwargs)
#         logger.debug('---------bcall done')

    '''
    timeout indicator
    '''    
    def timeOUT(self, **kwargs):
        print 'Time out occurred'



count_states = [{'name':'1','timeout':'4','timeoutSmTrigger':'timeOUT'}, '2', '3', 'done']
# count_states = [{'name':'1'}, '2', '3', 'done']

count_trans = [
    {'trigger':'increase', 'source':'1','dest':'2', 'before':'bcall'},
    ['increase', '2', '3'],
    ['decrease', '3', '2'],
    ['decrease', '2', '1'],
    ['done', '3', 'done'],
    ['reset', '*', '1']
]


counter = TimeoutMachine(name='Counter',states=count_states, transitions=count_trans, initial='1')

class ColModel(CntModel):
    def acall(self, **kwargs):
        print 'acall ',str(kwargs) 
#         logger.debug('-------acall done')



states = ['waiting', 'collecting', {'name': 'counting', 'children': counter}]
transitions = [
    {'trigger':'collect', 'source':'*','dest':'collecting', 'after':'acall'},
    ['wait', '*', 'waiting'],
    ['count', 'collecting', 'counting_1']
]

colm = ColModel() #composed model

collector = TimeoutMachine(name='Collector', model=[colm], states=states, transitions=transitions, initial='waiting')

collector.model.collect(a='acall1')  # collecting
collector.model.count()  # let's see what we got
collector.model.increase(a='inc1')  # counting_2
collector.model.increase(a='inc2')  # counting_3
collector.model.done()  # collector.state == counting_done
collector.model.wait()  # collector.state == waiting

logger.handlers=[]

谢谢!

您对 TimeoutState.enter/exit 的定义缺少位置参数 (*args)。机器将使用 self.enter(event_data)event_data 作为位置参数来调用状态。 这不适合 def(self, **kwargs),因为 **kwargs 仅映射带有关键字的参数(例如 key=value)。

错误发生在TimeoutState.exit,因为之前没有进入任何状态。 Machine.initial 将或多或少地 spawn 处于传递状态的模型而无需调用 preparebeforeenter.

我建议使用继承的 class 中的方法定义(就像在提到的 github 问题中一样),除非您需要 *args 和 [=23= 附带的灵活性]:

class TimeoutState(State):

    def __init__(self, name,  timeout=3, timeoutSmTrigger='timeOUT', *args, **kwargs):
        super(TimeoutState, self).__init__(name=name, *args, **kwargs)

    def enter(self, event_data):
        super(TimeoutState, self).enter(event_data)

    def exit(self, event_data):
        super(TimeoutState, self).exit(event_data)

您要查找的参数是 event_data 的属性。 因此,如果您将参数传递给触发器 (e.g. collector.model.count),您将在 event_data.args 处找到位置参数,在 event_data.kwargs 处找到关键字参数。此外,您将从 event_data.model.

获得模型

您可能想查看从何处获取参数,因为您似乎也尝试从 **kwargs 检索状态属性。这可能会导致工作代码,但似乎还有更多问题。例如,您在 getattr(kwargs.get('model', None), timeoutSmTrigger) 中链接 get 和 getattr 的方式不是很有帮助,因为如果 kwargs 不包含模型,则默认 None 肯定会引发 AttributeError