状态机:Python编程技巧-使用状态机



状态机从理论上说是几乎和计算机和编程相关每件事基础从实用角度来看状态机还有助于解决许多常见问题(特别适用于 Python 员)本文中David Mertz 讨论了何时以及如何使用 Python 编码状态机实际例子

什么是 Python?

Python 是由 Guido van Rossum 开发免费高级解释型语言其语法简单易懂而其面向对象语义功能强大(但又灵活)Python 可以广泛使用并具有高度可移植性

什么是状态机?

有关状态机个极度确切描述是它是个有向图形组节点和组相应转移组成状态机通过响应系列事件而“运行”每个事件都在属于“当前”节点转移控制范围内其中范围是节点个子集返回“下个”(也许是同个)节点这些节点中至少有个必须是终态当到达终态状态机停止

个抽象数学描述(就像我刚给出)并不能真正介绍说明在什么情况下使用状态机可以解决实际编程问题种策略就是将状态机定义成种强制性编程语言其中节点也是源码行从实用角度看这个定义尽管精确但它和第种描述都是纸上谈兵、毫不实用(对于介绍说明型、型或基于约束语言例如Haskell、Scheme 或 Prolog定会发生这种情况)

让我们尝试使用更适合身边实际任务例子来进行讨论逻辑上每个规则表达式都等价于个状态机而每个规则表达式语法分析器都实现这个状态机实际上大多数员编写状态机时并没有真正考虑到这

在以下这个例子中我们将研究状态机真正探索性定义通常我们有些区别思路方法来响应组有限数量事件某些情况下响应只取决于事件本身但在其它情况下适当操作取决于以前事件

本文中讨论状态机是高级机器其目是演示类问题编程解决方案如果有必要按响应事件行为类别来讨论编程问题那么您解决方案很可能是显式状态机

=atitle>文本处理状态机

最可能显式状态机个编程问题涉及到处理文本文件处理文本文件通常包括读取信息单元(通常叫做或行)然后对刚读取单元执行适当操作某些情况下这个处理是“无状态”(即每个这样单元都包含了足够信息可以正确确定要执行什么操作)在其它情况下即使文本文件不是完全无状态数据也只有有限上下文(例如操作取决于不比行号更多信息)但是在其它常见文本处理问题中输入文件是极具“状态”块数据含义取决于它前面串(也许是它后面串)报告、大型机数据输入、可读文本、编程源文件和其它种类文本文件都是有状态个简单例子是可能出现在 Python 源文件中行代码:

=section> myObject = SomeClass(this, that, other)






这行表示如果恰好有以下几行围绕着这则有部分内容区别:

=section> """How to use SomeClass: myObject = SomeClass(this, that, other) """






我们应知道我们处于“块引用” 状态 以确定这行代码是部分注释而不是 Python 操作

=atitle>何时不使用状态机

当开始为任何有状态文本文件编写处理器任务时问自己您希望在文件中找到什么类型输入项每种类型输入项都是种状态候选项这些类型共有几种如果数字很大或者不确定则状态机也许不是正确解决思路方法(在这种情况下某些数据库解决方案可能更适合)

还请考虑您是否需要使用状态机许多情况下最好从更简单思路方法入手也许会发现即使文本文件是有状态也有种简单思路方法可以分块读取它(其中每块是种类型输入值)实际上在单状态块中仅当文本类型的间转移需要基于内容计算时才有必要实现状态机

下面这个简单例子介绍说明了需要使用状态机情况请考虑用于将列数字划分成几块两个规则在第个规则中列表中零表示块的间间断第 2个规则中个块中元素总和超过 100 时会发生块的间间断由于它使用累加器变量来确定是否达到了阈值您不能“马上”看到子列表边界因此第 2个规则也许更适合于类似于状态机机制

稍微有些状态、但由 太适合用状态机处理文本文件例子是 风格 .ini 文件这种文件包括节头、注释和许多赋值例如:

=section> ; the colorscheme and userlevel [colorscheme] background=red foreground=blue title=green [userlevel] login=2 title=1






我们例子没有实际含义但它表明了 .ini 格式些有趣特性

  • 就某种意义而言类型由它确定(可能是分号、左花括号或字母)
  • 从另种角度看这种格式是“有状态关键字 "title" 大概表示如果它出现在每节中那么就有独立内容
您可以编写个有 COLORSCHEME 状态和 USERLEVEL 状态文本处理器这个仍处理每个状态赋值但这好象不是处理此问题 正确 思路方法例如可以使用 Python 代码在这个文本文件中只创建自然块如:


处理 .INI 文件分块 Python 代码
=section> =boldcode> import txt = open( 'hypothetical.ini').read sects = .split(txt, '[') =boldcode> for sect =boldcode> in sects: # do something with sect, like get its name # (the stuff up to ']') and read its assignments






或者如果愿意可以使用单个 current_section 变量来确定位置:


处理 .INI 文件计算 Python 代码
=section> =boldcode> for line =boldcode> in open( 'hypothetical.ini').readlines: =boldcode> line[0] '[': current_section = line(1:-2) =boldcode> el line[0] ';': =boldcode> pass # ignore comments
=boldcode> : apply_value(current_section, line)















=atitle>何时使用状态机

现在我们已经决定了如果文本文件“太简单”就不使用状态机让我们再研究 需要使用状态机情况本专栏中 最近篇文章 讨论了实用 Txt2Html它将“智能 ASCII”(包括本文)转换成 HTML让我们扼要重述

“智能 ASCII”是种文本格式它使用些间隔约定来区分文本块类型如头、常规文本、引语和代码样本虽然读者或作者能容易地通过查看分析这些文本块类型的间转移但却没有简单思路方法可以让计算机将“智能 ASCII”文件分割成组成它文本块不像 .ini 文件举例文本块类型可以任何顺序出现在任何情况下都没有单定界符来分隔块(空行 通常 分隔文本块但代码样本中空行却不定结束代码样本并且文本块不需要用空行来分隔)由于需要以区别方式重新格式化每个文本块以生成正确 HTML 输出状态机似乎就是自然解决方案

Txt2Html 阅读器般功能如下:

  1. 状态中启动
  2. 读取行输入
  3. 根据输入和当前状态转移到新状态或按适合当前状态方式处理该行
这个例子是有关您会遇到最简单情况但它介绍说明了我们描述过以下模式:


Python 中个简单状态机输入循环
=section> =boldcode> global state, blocks, bl_num, block #-- Initialize the globals state = "HEADER" blocks = [""] bl_num = 0 block = 1 =boldcode> for line =boldcode> in fhin.readlines: =boldcode> state "HEADER": # blank line means block of header =boldcode> blankln.match(line): block = 1 =boldcode> el textln.match(line): startText(line) =boldcode> el codeln.match(line): startCode(line) =boldcode> : =boldcode> block: startHead(line) =boldcode> : blocks[bl_num] = blocks[bl_num] + line =boldcode> el state "TEXT": # blank line means block of text =boldcode> blankln.match(line): block = 1 =boldcode> el headln.match(line): startHead(line) =boldcode> el codeln.match(line): startCode(line) =boldcode> : =boldcode> block: startText(line) =boldcode> : blocks[bl_num] = blocks[bl_num] + line =boldcode> el state "CODE": # blank line does not change state =boldcode> blankln.match(line): blocks[bl_num] = blocks[bl_num] + line =boldcode> el headln.match(line): startHead(line) =boldcode> el textln.match(line): startText(line) =boldcode> : blocks[bl_num] = blocks[bl_num] + line =boldcode> : =boldcode> raise ValueError, "unexpected input block state: "+state






可以用 Txt2Html 从中取出该代码源文件(请参阅 参考资料 )请注意:变量 state 声明为 global (如 startText )中更改它转移条件textln.match 是规则表达式模式但它们可能也是定制实际上以后会在中执行格式化状态机只将文本文件分析成 blocks 列表中带标签











=atitle>抽象状态机类

在表单和中使用 Python 实现抽象状态机很容易这使状态机模型比前个例子中简单条件块显得更突出(初看其中条件和其它条件没有什么区别)而且以下类及其关联处理在隔离状态中操作方面完成得很好许多情况下这改善了封装和可读性


文件:statemachine.py
=section> =boldcode> from =boldcode> import upper =boldcode> StateMachine : =boldcode> def __init__ (self): self.handlers = {} self.startState = None self.endStates = =boldcode> def add_state (self, name, handler, end_state=0): name = upper(name) self.handlers[name] = handler =boldcode> end_state: self.endStates.append(name) =boldcode> def _start (self, name): self.startState = upper(name) =boldcode> def run (self, cargo): =boldcode> try : handler = self.handlers[self.startState] =boldcode> except : =boldcode> raise "InitializationError", "must call ._start before .run" =boldcode> =boldcode> not self.endStates: =boldcode> raise "InitializationError", "at least _disibledevent=> while 1: (State, cargo) = handler(cargo) =boldcode> upper(State) =boldcode> in self.endStates: =boldcode> =boldcode> : handler = self.handlers[upper(State)]





StateMachine 类实际上正是抽象状态机所需要使用 Python 传递对象是如此简单和其它语言中相似类比较这个类所需使用行数非常少

要真正 使用 StateMachine需要为每个要使用状态创建些处理处理必须符合模式它循环处理事件直到要转移到另个状态此时处理应该将个字节组(它包括新状态名称以及新状态处理需要任何 cargo)传递回去

StateMachine 类中将 cargo 用作变量做法将封装状态处理所需数据(该状态处理不必 cargo 变量)状态处理使用 cargo 来传递下个处理所需内容于是新处理可以接管前个处理遗留工作 cargo 通常包括文件句柄它允许下个处理可以在前个处理停止后读取更多数据 cargo 还可能是数据库连接、复杂类例子或带有几个项列表

现在让我们研究测试样本在本例中(在以下代码举例中概述)cargo 只是不断将反馈传送给迭代个数字只要 val 处于某个范围内val 个值永远只是 math_func(val) 返回了超出范围那么该值将传送到另个处理或者状态机在个什么也不做终态处理后就退出举例介绍说明了件事: 事件不必是输入事件它也可以是计算事件(这种情况很少)状态处理相互的间区别只是在输出它们处理事件时使用区别标记比较简单没必要使用状态机但它很好地介绍说明了概念代码也许比解释更易于理解!












文件:statemachine_test.py
=section> =boldcode> from statemachine =boldcode> import StateMachine =boldcode> def _disibledevent=>(val): =boldcode> pr "ONES State: ", =boldcode> while 1: =boldcode> val <= 0 =boldcode> or val >= 30: State = "Out_of_Range" ; =boldcode> el 20 <= val < 30: State = "TWENTIES"; =boldcode> el 10 <= val < 20: State = "TENS"; =boldcode> : =boldcode> pr " @ %2.1f+" % val, val = math_func(val) =boldcode> pr " >>" =boldcode> (State, val) =boldcode> def tens_counter (val): =boldcode> pr "TENS State: ", =boldcode> while 1: =boldcode> val <= 0 =boldcode> or val >= 30: State = "Out_of_Range"; =boldcode> el 1 <= val < 10: State = "ONES"; =boldcode> el 20 <= val < 30: State = "TWENTIES"; =boldcode> : =boldcode> pr " #%2.1f+" % val, val = math_func(val) =boldcode> pr " >>" =boldcode> (State, val) =boldcode> def twenties_counter (val): =boldcode> pr "TWENTIES State:", =boldcode> while 1: =boldcode> val <= 0 =boldcode> or val >= 30: State = "Out_of_Range"; =boldcode> el 1 <= val < 10: State = "ONES"; =boldcode> el 10 <= val < 20: State = "TENS"; =boldcode> : =boldcode> pr " *%2.1f+" % val, val = math_func(val) =boldcode> pr " >>" =boldcode> (State, val) =boldcode> def math_func (n): =boldcode> from math =boldcode> import sin =boldcode> abs(sin(n))*31 =boldcode> __name__ "____": m = StateMachine m.add_state( "ONES", _disibledevent=>








Tags:  vhdl状态机 什么是状态机 有限状态机 状态机

延伸阅读

最新评论

发表评论