python 初探状态机transiti

发布时间:2019-09-13 09:25:16编辑:auto阅读(1644)

    偶然接触一个python的关于状态机的库,简单demo如下:
    # coding=utf-8
    ##############################################################
    # 目标:"solid", "liquid", "gas", "plasma" 四种互相转换...
    # 环境: pip install transitions
    # 这个库简直碉堡了...
    ##############################################################
    from transitions import Machine
    
    class Matter(object):
        pass
    
    model = Matter()
    
    # 定义状态
    states = ["solid", "liquid", "gas", "plasma"]
    # 定义状态转移
    transitions = [
        {'trigger':'melt', 'source': 'solid', 'dest': 'liquid'},
        {'trigger':'evaporate', 'source': 'solid', 'dest': 'gas'},
        {'trigger':'sublimate', 'source': 'liquid', 'dest': 'gas'},
        {'trigger':'ionize', 'source': 'gas', 'dest': 'plasma'},
    ]
    # 初始化
    machine = Machine(model=model, states=states, transitions=transitions, initial='solid')
    
    print model.state
    model.melt()
    print model.state
    对源代码一探究竟。发现看不懂的地方甚多,那就分解一步步去解析吧。
    Machine
    —————> States = {'s1':s1, 's2':s2, 's3': s3}
    —————> Events = {e1, e2} e1—————>Transition = [t1,t2,t3...]
    —————> models = [m1, m2] m1 —————> to_s1(), to_s2()
    大概就是Machine这个类里面维护比较重要的三个元素,States={},Events={}, models= [],接下来先看怎么填充这3个元素。


    0、
    pip install transitions==0.6.1
    python == 2.7.10

    1、
    按照demo来说,我们传入的States和transitions:
    States = ["solid", "liquid", "gas", "plasma"]
    transitions = [
        {'trigger':'melt', 'source': 'solid', 'dest': 'liquid'},
        {'trigger':'evaporate', 'source': 'solid', 'dest': 'gas'},
        {'trigger':'sublimate', 'source': 'liquid', 'dest': 'gas'},
        {'trigger':'ionize', 'source': 'gas', 'dest': 'plasma'},
    ]

    2、
    States
    Machine的构造函数中,传入了一个状态list,触发self.add_states(states):
            if states is not None:
                self.add_states(states)
    然后在add_states()中,用字符串state分别创建一个State实例,然后把这个实例加入到字典self.states中,key就是字符串state
            for state in states:
                if isinstance(state, string_types):
                    state = self._create_state(state, on_enter=on_enter, on_exit=on_exit,
                       ignore_invalid_triggers=ignore, **kwargs)
                self.states[state.name] = state
    所以到这里我们的Machine实例字典参数如下:
    self.states = {
        'solid': <State('solid')@4516320272>, 
        'liquid': <State('liquid')@4516320336>, 
        'gas': <State('gas')@4516320400>, 
        'plasma': <State('plasma')@4516320464>
    }
    同样在add_states()中,最后来一句:
            if self.auto_transitions:
                for s in self.states.keys():
                    self.add_transition('to_%s' % s, self.wildcard_all, s)
    这就很有趣了,每一个状态,都触发一次add_transition(),比如我们四个状态中的solid,触发时名字为to_solid,self.wildcard_all就是*号,表示source源状态,solid表示desc目标状态。那么看下add_transiton()给我们带来什么呢?
            if trigger not in self.events:
                self.events[trigger] = self._create_event(trigger, self)
    因为to_solid一定不在self.events={}中,所以if条件为True,先是用trigger=to_solid创建一个Event实例,然后加到self.events这个字典中。
    所以到这里我们的Machine实例字典参数如下:
    self.events = {
        'to_liquid': <Event('to_liquid')@4516320592>, 
        'to_gas': <Event('to_gas')@4516320912>, 
        'to_plasma': <Event('to_plasma')@4516321232>, 
        'to_solid': <Event('to_solid')@4516320528>
    }
    继续往下走,add_transition()接下来执行:
            if isinstance(source, string_types):
                source = list(self.states.keys()) if source == self.wildcard_all else [source]
            else:
                source = [s.name if self._has_state(s) else s for s in listify(source)]
    这是算出source,如果我们进来的参数是self.wildcard_all,那么source=["solid", "liquid", "gas", "plasma"],否则source=["solid"],add_transition()接下来执行:
            for s in source:
                d = s if dest == self.wildcard_same else dest
                if self._has_state(d):
                    d = d.name
                t = self._create_transition(s, d, conditions, unless, before,
                                            after, prepare, **kwargs)
                self.events[trigger].add_transition(t)
    这是对于每一个source,我们都算出一对Source2Dest如:("solid", "solid"),用这一对去生成一个Transition实例t,然后self.events["to_solid"]这个event,添加这个t到event.transitions中。
    所以到了这里我们的Machine实例参数如下:
    self.events["to_solid"].transitions = {
        'solid': [<Transition('solid', 'solid')@4516320656>],
         'plasma': [<Transition('plasma', 'solid')@4516320848>], 
        'gas': [<Transition('gas', 'solid')@4516320784>], 
        'liquid': [<Transition('liquid', 'solid')@4516320720>]
    }
    
    self.events["to_liquid"].transitions = {
        'solid': [<Transition('solid', 'liquid')@4516320656>],
         'plasma': [<Transition('plasma', 'liquid')@4516320848>], 
        'gas': [<Transition('gas', 'liquid')@4516320784>], 
        'liquid': [<Transition('liquid', 'liquid')@4516320720>]
    }
    
    self.events["to_gas"].transitions = {
        'solid': [<Transition('solid', 'gas')@4516320656>],
         'plasma': [<Transition('plasma', 'gas')@4516320848>], 
        'gas': [<Transition('gas', 'gas')@4516320784>], 
        'liquid': [<Transition('liquid', 'gas')@4516320720>]
    }
    
    self.events["to_plasma"].transitions = {
        'solid': [<Transition('solid', 'plasma')@4516320656>],
         'plasma': [<Transition('plasma', 'plasma')@4516320848>], 
        'gas': [<Transition('gas', 'plasma')@4516320784>], 
        'liquid': [<Transition('liquid', 'plasma')@4516320720>]
    }

    3、
    Events
    Machine的构造函数中,传入了transitions的list,触发了add_transitions():
            if transitions is not None:
                self.add_transitions(transitions)
    查看下add_transitions()发生了什么:
            for t in listify(transitions):
                if isinstance(t, list):
                    self.add_transition(*t)
    好的,是分别触发add_transition()函数,那么根据1中分析,我们自定义传入了4个trigger,那么此时此刻的self.events如下:
    self.events = {
        'to_liquid': <Event('to_liquid')@4516320592>, 
        'to_gas': <Event('to_gas')@4516320912>, 
        'to_plasma': <Event('to_plasma')@4516321232>, 
        'to_solid': <Event('to_solid')@4516320528>,
        'sublimate': <Event('sublimate')@4516446608>,
        'evaporate': <Event('evaporate')@4516446480>,
        'melt': <Event('melt')@4516446416>, 
        'ionize': <Event('ionize')@4516446736>
    }
    然后除了to_liquid, to_gas, to_plasma, to_solid,其他的参数如下:
    self.events["melt"].transitions = {
       'solid': [<Transition('solid', 'liquid')@4438964880>]
    }
    
    self.events["ionize"].transitions = {
      'gas': [<Transition('gas', 'plasma')@4438965264>] 
    }
    
    self.events["sublimate"].transitions = {
        'liquid': [<Transition('liquid', 'gas')@4438965136>]
    }
    
    self.events["evaporate"].transitions = {
        'solid': [<Transition('solid', 'gas')@4438965008>]
    }

    4、
    model
    Machine的构造函数中,传入了model这list,触发了add_model():
            if model:
                self.add_model(model)
    在add_model()中,先是遍历model(没错,它被变成一个list,如果单个元素,就是[model])。这里我们就只看一个model。
            if hasattr(model, 'trigger'):
                logger.warning("%sModel already contains an attribute 'trigger'. Skip method binding ", self.name)
            else:
                model.trigger = partial(get_trigger, model)
    这段代码是先给model加一个函数trigger(),以后就能够用model.trigger()调用啦。
    实际上调用的是偏函数get_trigger(model, trigger_name).
        def get_trigger(model, trigger_name, *args, **kwargs):
            func = getattr(model, trigger_name, None)
            if func:
                return func(*args, **kwargs)
            raise AttributeError("Model has no trigger named '%s'" % trigger_name)
    有意思,所以以后我们就能用model.trigger("melt"),来调用某一个触发函数。

    接着继续在add_model()中:
        for trigger, _ in self.events.items():
            self._add_trigger_to_model(trigger, model)
    这个是遍历我们Machine实例中的self.events的键值对,拿到每个触发名字:to_liquid, to_solid, to_gas, to_plasma, melt, ionize, sublimate, evaporate,遍历调用self._add_trigger_to_model(trigger, model):
        def _add_trigger_to_model(self, trigger, model):
            trig_func = partial(self.events[trigger].trigger, model)
            setattr(model, trigger, trig_func)
    其实这个还是给model绑定偏函数,以后我们就能这么调用model.melt()。
    所以到了这里我们Machine实例关于model参数如下:
    model.trigger("xxx")
    model.to_liquid()
    model.to_solid()
    model.to_gas()
    model.to_plasma()
    model.melt()
    model.mionize()
    model.sublimate()
    model.evaporate()
    接着继续在add_model()中:
        for _, state in self.states.items():
            self._add_model_to_state(state, model)
    和上面差不多,遍历调用_add_model_to_state(state, model)
        def _add_model_to_state(self, state, model):
            setattr(model, 'is_%s' % state.name,
                    partial(self.is_state, state.name, model))
    所以到了这里我们Machine实例关于model参数如下:
    model.is_solid
    model.is_liquid
    model.is_gas
    model.is_plasms
    model.state # 这个是model.setState()把初始化状态如solid设置过来生成的属性
    好了,我们知道了model现在多了很多函数和属性。

    好的,现在我们具体来看看调用model.melt()到底发生了什么呢?它其实是调用self.events['melt'].trigger(model),
    好的,看看self.events['melt'].trigger(model)里面是啥?
        def trigger(self, model, *args, **kwargs):
            f = partial(self._trigger, model, *args, **kwargs)
            return self.machine._process(f)
    好的,原来是调用self.events['melt']._trigger(model),那么这个实际处理函数_trigger()里面又是啥呢?
            state = self.machine.get_state(model.state)
            if state.name not in self.transitions:
                msg = "%sCan't trigger event %s from state %s!" % (self.machine.name, self.name,
                                                                   state.name)
                if state.ignore_invalid_triggers:
                    logger.warning(msg)
                    return False
                else:
                    raise MachineError(msg)
    第一句就先找出当前model的状态是什么:<State('solid')@4372239248>.
    然后判断state.name 在不在 当前event的transitions中:{'solid': [<Transition('solid', 'liquid')@4372347728>]}
    好吧,确实存在,那么就不用if语句块啦。_trigger()继续往下:
    event_data = EventData(state, self, self.machine, model, args=args, kwargs=kwargs)
    这个生成一个event_data实例,顾名思义,它就是一放数据的,里面放了当前machine,当前state,当前event,当前的model,transitions,执行transition()的结果result,当然不仅仅是放数据而已,它还会update(),更新machine状态。好吧,_trigger()继续往下:
                for t in self.transitions[state.name]:
                    event_data.transition = t
                    if t.execute(event_data):
                        event_data.result = True
                        break
    如上我们的self.transitions['solid']只有一个元素t:{'solid': [<Transition('solid', 'liquid')@4372347728>]},把这个元素t赋值给event_data后,开始执行t,等会执行结果还是要放到event_data中呢。
    总结下,到这里我们执行model.melt(),其实就是执行self.events['melt'].transitions['solid'].execute().

    好的,那我们继续看看这个t.execute(event_data)怎么个执行法。
    因为我们没带什么prepare, conditions等高级参数,所以我们把这个execute()简化成:
        def execute(self, event_data):
            self._change_state(event_data)
            return True
    好吧,继续看看transition._change_state(),顾名思义,它就是专门处理状态的更新:
        def _change_state(self, event_data):
            event_data.machine.get_state(self.source).exit(event_data)
            event_data.machine.set_state(self.dest, event_data.model)
            event_data.update(event_data.model)
            event_data.machine.get_state(self.dest).enter(event_data)
    没什么好说的,就是单纯的处理前状态"solid"的exit(),把当前状态置成desc的状态="liquid", 返回True。


    总结下,Machine就是有两个self.events={}, self.states=[]辅助围着self.model转,维护好这个model的状态,即可!
    这个transitions库大量运用偏函数呀和动态生成属性函数什么的,不过这也正常,毕竟从demo上看来,demo越简单,底下的操作越复杂。

    以上


关键字