如何在pytransition的条件函数中传递事件参数

How to pass event parameters in condition function in pytransition

我正在尝试将参数传递给条件。但它给出了以下错误

代码:

from transitions import Machine

class evenodd(object):
    def get_no(self, event):
        self.no = event.kwargs.get('no', 0)
    def calc_mod(self, event):
        self.mod = event.kwargs.get('mod', self.no%2)
    
    def is_even(self, event):
        if self.mod == 0:
            return True
        
obj = evenodd()
machine = Machine(obj, ['init', 'getno', 'even', 'odd'], send_event=True, initial='init', ignore_invalid_triggers=True, auto_transitions=False)
machine.add_transition('enterno', 'init', 'getno', before='get_no')
machine.add_transition('isevenodd', 'getno', 'even', before='calc_mod', conditions=['is_even'])
machine.add_transition('isevenodd', 'getno', 'odd', before='calc_mod', conditions=['is_odd']) 


s_state = obj.state
print("state --> "+s_state)
trigger = machine.get_triggers(s_state)[0]
print("transition --> "+trigger)
obj.enterno(2)
s_state = obj.state
print("state --> "+s_state)
trigger = machine.get_triggers(s_state)[0]
print("transition --> "+trigger)
obj.isevenodd()
s_state = obj.state
print("state --> "+s_state)

错误:

state --> init
transition --> enterno
state --> getno
transition --> isevenodd
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-75-d8ede3775dc5> in <module>
      8 trigger = machine.get_triggers(s_state)[0]
      9 print("transition --> "+trigger)
---> 10 obj.isevenodd()
     11 s_state = obj.state
     12 print("state --> "+s_state)

~\Anaconda3\envs\ml_nlp_cpu\lib\site-packages\transitions\core.py in trigger(self, model, *args, **kwargs)
    388         # Machine._process should not be called somewhere else. That's why it should not be exposed
    389         # to Machine users.
--> 390         return self.machine._process(func)
    391 
    392     def _trigger(self, model, *args, **kwargs):

~\Anaconda3\envs\ml_nlp_cpu\lib\site-packages\transitions\core.py in _process(self, trigger)
   1112             if not self._transition_queue:
   1113                 # if trigger raises an Error, it has to be handled by the Machine.process caller
-> 1114                 return trigger()
   1115             else:
   1116                 raise MachineError("Attempt to process events synchronously while transition queue is not empty!")

~\Anaconda3\envs\ml_nlp_cpu\lib\site-packages\transitions\core.py in _trigger(self, model, *args, **kwargs)
    406                 raise MachineError(msg)
    407         event_data = EventData(state, self, self.machine, model, args=args, kwargs=kwargs)
--> 408         return self._process(event_data)
    409 
    410     def _process(self, event_data):

~\Anaconda3\envs\ml_nlp_cpu\lib\site-packages\transitions\core.py in _process(self, event_data)
    415             for trans in self.transitions[event_data.state.name]:
    416                 event_data.transition = trans
--> 417                 if trans.execute(event_data):
    418                     event_data.result = True
    419                     break

~\Anaconda3\envs\ml_nlp_cpu\lib\site-packages\transitions\core.py in execute(self, event_data)
    260         _LOGGER.debug("%sExecuted callbacks before conditions.", event_data.machine.name)
    261 
--> 262         if not self._eval_conditions(event_data):
    263             return False
    264 

~\Anaconda3\envs\ml_nlp_cpu\lib\site-packages\transitions\core.py in _eval_conditions(self, event_data)
    241     def _eval_conditions(self, event_data):
    242         for cond in self.conditions:
--> 243             if not cond.check(event_data):
    244                 _LOGGER.debug("%sTransition condition failed: %s() does not return %s. Transition halted.",
    245                               event_data.machine.name, cond.func, cond.target)

~\Anaconda3\envs\ml_nlp_cpu\lib\site-packages\transitions\core.py in check(self, event_data)
    179         predicate = event_data.machine.resolve_callable(self.func, event_data)
    180         if event_data.machine.send_event:
--> 181             return predicate(event_data) == self.target
    182         return predicate(*event_data.args, **event_data.kwargs) == self.target
    183 

<ipython-input-74-5dc86072aabd> in is_even(self, event)
      7     def is_even(self, event):
      8         """ Basically a coin toss. """
----> 9         if self.mod == 0:
     10             return True
     11 

AttributeError: 'evenodd' object has no attribute 'mod'

如何与每个回调共享变量 no 和 mod。我尝试使用该事件。 在这个简单的示例中,我尝试创建一个状态机以根据给定输入是偶数还是奇数来达到状态。

您可以共享变量作为模型的属性。不过,您需要考虑回调执行顺序。根据 transitions documentation 回调按以下顺序执行:

  • 'machine.prepare_event'
  • 'transition.prepare'
  • 'transition.conditions'
  • 'transition.unless'
  • 'machine.before_state_change'
  • 'transition.before'
  • 'state.on_exit'
  • <STATE CHANGE>
  • 'state.on_enter'
  • 'transition.after'
  • 'machine.after_state_change'
  • 'machine.finalize_event'

您可以看到 条件被评估后触发 before 并且转换 肯定会 发生。因此,self.modis_even 中不可用,因为 calc_mod 尚未被调用。该列表还显示了如何处理此问题:Execute calc_mod in prepare instead of before:

machine.add_transition('isevenodd', 'getno', 'even', prepare='calc_mod', conditions=['is_even'])
machine.add_transition('isevenodd', 'getno', 'odd', prepare='calc_mod', conditions=['is_odd'])

prepare 正是针对条件检查需要一些设置的用例引入的。如果还需要 'tear down',则可以使用 machine.finalize_event,无论是否发生转换,它都会被调用。说到回调解析顺序:您可以将条件检查传递给 unless,如果它们的计算结果为真,这将停止转换。您可以将 conditions='is_odd' 替换为 unless='is_even'.

如果您不介意某些隐式逻辑,您可以完全放弃 'is_odd' 检查。一组有效的触发器总是按照它们被添加的顺序进行评估。这意味着只有当 'getno' -> 'even' 被发现无效时才会考虑 'getno' -> 'odd'。 Design-wise,添加 last 的无条件转换将充当 'else' 子句,当无法满足先前的条件集时将执行该子句。

如何将变量传递给回调

一种方法是在机器上处理 send_event=True 时传递给回调的 event 对象。您还可以将变量作为触发器参数传递:

from transitions import Machine


class evenodd(object):

    def get_no(self, no=0, **kwargs):
        self.no = no

    def calc_mod(self, mod=None, **kwargs):
        self.mod = mod if mod else self.no % 2

    def is_even(self):
        return self.mod == 0


obj = evenodd()
machine = Machine(obj, ['init', 'getno', 'even', 'odd'], initial='init', 
                  ignore_invalid_triggers=True, auto_transitions=False)
machine.add_transition('enterno', 'init', 'getno', before='get_no')
machine.add_transition('isevenodd', 'getno', 'even', prepare='calc_mod', conditions=['is_even'])
machine.add_transition('isevenodd', 'getno', 'odd', prepare='calc_mod', conditions=['is_odd'])

obj.enterno(no=2)
obj.isevenodd()
print("state --> " + obj.state)

如果这样做,您必须确保每个回调都可以使用向它们抛出的所有 参数。使用 kwargs 可以很容易地使用 'discard' 不需要的参数。我还建议 always 将参数作为关键字参数传递,即使也支持位置参数。根据我的经验,这使代码更易于理解(例如更易于阅读方法定义)并减少错误源(例如错误的参数映射)。