状态机的概念和在Python下使用状态机的教程

发布时间:2026/8/28 6:13:50
状态机的概念和在Python下使用状态机的教程 什么是状态机对于状态机, 有这样一个极为确切的描述, 它是一种有向图形, 这图形是由一组节点以及一组相应的转移函数共同构成的。状态机的运行, 是通过对一系列事件做出响应来达成的。每一个事件, 都处于属于“当前”节点的转移函数的控制范围以内, 这里函数的范围, 其实是节点的一个子集。该函数会返回“下一个”有可能是同一个节点。在这些节点当中, 至少得有一个必然是终态。一旦到达终态, 状态机会停止运行。但一个抽象的数学描述, 如同我刚给出的那般, 并不能切实表明在何种情形下运用状态机能够解决实际编程问题。另一种策略, 乃是把状态机定义为一种强制性编程语言, 其中节点同为源码行。从实用视角来看, 这个定义尽管精准, 然而它跟第一种描述一样, 皆为纸上空谈、毫无实用价值。对于说明型、函数型或基于约束的语言, 诸如 、 或 , 不一定会出现这种状况尝试着让我们, 去使用更适配周边实际状况下任务的例子, 来开展讨论, 逻辑层面上, 任意一个规则表达式, 都等同于一个状态机, 并且每一个规则表达式的语法分析器, 都去实现这个状态机械, 实践当中, 绝大多数程序员在编写状态机之际, 并没有切实地考虑到这一点。于以下这个例子里头, 我们将会去研究状态机的真正探索性定义。一般而言, 我们存在一些不一样的方法用以响应一组数量有限的事件。在某些情形之下, 响应仅仅取决于事件自身。然而在其他情形之下, 恰当的操作取决于先前出现的事件。当前所讲的状态机属于高级机器, 它存在有一定目的, 这个目的是用来演示一类有关于问题的编程解决办法。要是存在必要, 按照响应事件行为的这些特定类别去讨论编程事项的话, 对于您而言, 所给出的解决方案极有可能是明确的状态机。文本处理状态机一个最有可能调用显式状态机的编程问题, 与处理文本文件有关, 处理文本文件常涵盖读取信息单元, 一般称这些读取单元为字符或者行, 之后针对才读取的单元采取恰当行动 , 在某些情形下, 这种处理属于“无状态的”, 也即是每个这类单元都含有充足的信息, 能够准确判定要执行怎样的操作 , 在其他情形下, 哪怕文本文件并非全然无状态, 数据也仅有有限的上下文, 比如操作依赖的信息不比行号更多。然而, 于其他平常文本处理问题里, 输入文件是有着很强“状态”属性的。每一块数据的意义视其前面的字符串有可能是它后面的字符串而定。报告、大型机数据输入、可读文本、编程源文件还有其他各类文本文件皆是要有状态的。一个简单的示例是或许存于源文件中的一行代码:myObject SomeClass(this, that, other)这一行所表达的意思是, 要是正好存在着以下的几行围绕于这一行, 那么就会有部分的内容并不一样。How to use SomeClass: myObject SomeClass(this, that, other) 我们应当清楚知晓, 我们正处于一种名为“块引用”的状态, 这样做的目的在于, 能够依此来判定此行程代码, 它是属于注释内容的一部分, 而并不是操作行为。何时不使用状态机当着手去做那个为有着任何状态情况的文本文件去编写处理器的任务之际, 问一下你自己, 你期望在文件里寻觅到什么样类型的输入项。每一种类型的输入项都是一种状态的可供选项, 那这些类型总共涵盖几种。要是这个数字非常大又或者不确切明了, 那么状态机说不定并非是恰当的解决办法。处于这种情形下, 某些数据库方面的解决方案可能会更加适宜仍请思索您是否有使用状态机的需求。诸多情形下, 起始于更为简易的方式为宜。有可能找寻到哪怕文本文件呈现有状态属性 , 也能以一种简便方式按块读取 , 其中每个块归属于一种输入值类型。事实上 , 于单状态块内 , 仅在文本类型过渡需依赖内容予以计算时 , 实现状态机方可称得上有一定必要性。这儿有个比较简单的例子, 它说明了那种需要用到状态机的情形。请去思考一下用于把一列数字划分成好几个块的那两条规则。其中第一条规则里, 列表当中的零代表着块与块之间的间断。而在第二条规则里, 当某一个块里边的元素总和超过了整整100的时候, 块与块之间就会出现间断的情况。因为是依靠一个累加器变量来判定是不是跨越了阈值, 所以你没办法“随即”看到子列表的边界。如此一来, 第二条规则或许会更契合那种相当于状态机的机制。是那种稍微有着些许状态, 然而却又不太适宜运用状态机来进行处理的文本文件的例子, 是风格的.ini 文件, 这种文件涵盖着节头、注释以及诸多赋值, 比如:; set the colorscheme and userlevel [colorscheme] backgroundred foregroundblue titlegreen [userlevel] login2 title1这些例子于我们而言并无实际所指之意, 然其却彰显出了.ini 格式之中某些饶有趣味的性质。换个角度来讲, 每行的类型, 依靠它开头的那个字符来判定, 这个字符有可能是分号, 有可能是左花括号, 还有可能是字母。从另外一种视角去看, 这般格式是“具备状态的”, 缘由在于关键字“title”大致意味着要是它于每一节里出现, 那么便存在相应独立的内容。有一个文本处理器程序, 它存在状态, 且有状态, 于此程序仍会针对每个状态去处理赋值情况, 然而, 这似乎并非处理该问题的正确办法, 比如说, 能运用代码在此文本文件之中仅创建自然块, 像这样:处理 .INI 文件的分块 代码import string txt open( hypothetical.ini).read() sects string.split(txt, [) for sect in sects: # do something with sect, like get its name # (the stuff up to ]) and read its assignments或者如果愿意可以使用单个 变量来确定位置处理 .INI 文件的计算 代码for line in open( hypothetical.ini).readlines(): if line[0] [: current_section line(1:-2) elif line[0] ;: pass # ignore comments else : apply_value(current_section, line)何时使用状态机当前, 我们已然做出决定, 要是文本文件“太过简易”, 那就不会运用状态机, 咱们再去探究需要用到状态机的情形。本专栏里近期的一篇文章探讨了实用程序, 它把“智能 ASCII”涵盖本文转化成 HTML。咱们简要重新叙述一下。“智能 ASCII”属于一种文本格式, 它借助一些间隔约定, 用以区别文本块的类型, 像是头、常规文本、引语以及代码样本。尽管读者或者作者能够较为轻易地经由查看分析这些文本块类型之间的转移, 然而却不存在简单的办法, 能够让计算机把“智能 ASCII”文件分割成构成它的文本块。跟 .ini 文件示例不一样, 文本块类型能够以任何顺序出现。任何情形下, 都不存在单一的定界符用以分隔块, 空行, 通常情况下, 是用来分隔文本块的, 然而, 代码样本当中的空行, 却并非一定意味着能够终结代码样本, 而文本块, 也并不一定得借助空行来进行分隔。因为, 要以不同的方式对每个文本块重新进行格式化, 以此来生成正确的HTML输出, 所以, 状态机似乎就成为了自然而然的解决方案。阅读器的一般功能如下这个例子, 是关乎您将会碰到的最为简单的情形, 然而它阐释了我们曾经描述过的如下模式。中一个简单的状态机输入循环global state, blocks, bl_num, newblock #-- Initialize the globals state HEADER blocks [] bl_num 0 newblock 1 for line in fhin.readlines(): if state HEADER: # blank line means new block of header if blankln.match(line): newblock 1 elif textln.match(line): startText(line) elif codeln.match(line): startCode(line) else : if newblock: startHead(line) else : blocks[bl_num] blocks[bl_num] line elif state TEXT: # blank line means new block of text if blankln.match(line): newblock 1 elif headln.match(line): startHead(line) elif codeln.match(line): startCode(line) else : if newblock: startText(line) else : blocks[bl_num] blocks[bl_num] line elif state CODE: # blank line does not change state if blankln.match(line): blocks[bl_num] blocks[bl_num] line elif headln.match(line): startHead(line) elif textln.match(line): startText(line) else : blocks[bl_num] blocks[bl_num] line else : raise ValueError, unexpected input block state: state能够运用下载从中提取该代码的源文件于参考资料处可进行查阅。请对此加以留意: 变量在函数诸如()中改变其值时声明为。转移条件, 像.match()这般, 属于规则表达式模式, 不过它们也兴许是定制函数。实际上, 日后会于程序里开展格式化操作。状态机仅仅把文本文件解析成列表中带有标签的块。抽象状态机类使用它在表单及函数里来达成抽象状态机是十分轻易的。这致使程序的状态机模型于简单条件块那示例情况里较突出好些乍一看, 这里面的条件跟别的条件没什么两样。并且, 以下这些种类及其关联的处理程序, 在分开状态中进行操作这块儿做得相当不错。好多情景下, 这对封装以及可读性予以了改善。文件.pyfrom string import upper class StateMachine : def __init__ (self): self.handlers {} self.startState None self.endStates [] def add_state (self, name, handler, end_state0): name upper(name) self.handlers[name] handler if end_state: self.endStates.append(name) def set_start (self, name): self.startState upper(name) def run (self, cargo): try : handler self.handlers[self.startState] except : raise InitializationError, must call .set_start() before .run() if not self.endStates: raise InitializationError, at least one state must be an end_state while 1: (newState, cargo) handler(cargo) if upper(newState) in self.endStates: break else : handler self.handlers[upper(newState)]类, 实际上, 恰恰是抽象状态机所必需的。鉴于运用传递函数对象这般容易, 跟其它语言里的类似类作比较, 此种类所需运用的行数极度少。就要切实地运用那种类而言, 得针对每一个打算运用的状态构建一批处理程序。那些处理程序务必契合模式。它会循环往复地处理相应事件, 一直持续到要转向另外一个状态这般的情况, 在这个时候, 处理程序理应把一组字节这组字节涵盖了新状态的名称以及新的状态处理程序所必备的任何货物传递回去。有一种做法, 是在类里面把cargo用作变量, 这种做法能够封装去处理状态所需要使用到的数据, 而拥有该状态处理程序并不一定非要调用它的那个cargo变量。状态处理程序会借助cargo来交付下一个处理程序所需要的东西, 这样一来, 新的处理程序就能够接手前一个处理程序遗留下来的工作。cargo一般是包含文件句柄在内的, 文件句柄使得下一个处理程序能够在前一个处理程序停下之后还能读取更多的数据。cargo也没准是数据库连接, 或者是复杂的类实例, 又或者是带有几个项的列表情况。当前, 咱们来对测试样本着手开展研究。于本示例之中于下述代码示例里予以概述, cargo仅仅是持续把反馈传递给迭代函数的一个单一数字。只要val处在某个特定范围内, 那么val的下一个数值始终仅仅只是(val)。一旦函数返回了超出该范围的值, 那样此值就会被传送到另外一个处理程序处, 或者状态机在调用了一个什么事情都不做的终态处理程序以后便会退出进而终止运行。示例表明了这样一件事情: 事件不一定非得是输入事件。它同样也能够是计算事件这种情形成立的概率极小。在输出它们所处理的事件之际, 状态处理程序相互间的区别仅仅在于运用不同的标记, 该函数颇为简单, 没有必要采用状态机, 然而它对概念作出了很好的说明, 代码或许比解释更便于理解文件.pyfrom statemachine import StateMachine def ones_counter (val): print ONES State: , while 1: if val 0 or val 30: newState Out_of_Range ; break elif 20 val 30: newState TWENTIES; break elif 10 val 20: newState TENS; break else : print %2.1f % val, val math_func(val) print return (newState, val) def tens_counter (val): print TENS State: , while 1: if val 0 or val 30: newState Out_of_Range; break elif 1 val 10: newState ONES; break elif 20 val 30: newState TWENTIES; break else : print #%2.1f % val, val math_func(val) print return (newState, val) def twenties_counter (val): print TWENTIES State:, while 1: if val 0 or val 30: newState Out_of_Range; break elif 1 val 10: newState ONES; break elif 10 val 20: newState TENS; break else : print *%2.1f % val, val math_func(val) print return (newState, val) def math_func (n): from math import sin return abs(sin(n))*31 if __name__ __main__: m StateMachine() m.add_state( ONES, ones_counter) m.add_state( TENS, tens_counter) m.add_state( TWENTIES, twenties_counter) m.add_state( OUT_OF_RANGE, None, end_state1) m.set_start( ONES) m.run(1)