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

什么是状态机?

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

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

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

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

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

文本处理状态机

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

myObject = SomeClass(this, that, other)

这行表示,如果恰好有以下几行围绕着这一行,则有部分内容不同:

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

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

何时不使用状态机

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

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

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

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

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

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

    就某种意义而言,每一行的类型由它的第一个字符确定(可能是分号、左花括号或字母)。
    从另一种角度看,这种格式是“有状态的”,因为关键字 "title" 大概表示如果它出现在每一节中,那么就有独立的内容。

您可以编写一个有 COLORSCHEME 状态和 USERLEVEL 状态的文本处理器程序,这个程序仍处理每个状态的赋值。但这好象不是处理此问题的 正确 方法。例如,可以使用 Python 代码在这个文本文件中只创建自然块,如:
处理 .INI 文件的分块 Python 代码

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

或者,如果愿意,可以使用单个 current_section 变量来确定位置:
处理 .INI 文件的计算 Python 代码

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)

何时使用状态机

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

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

Txt2Html 阅读器的一般功能如下:

  •     在初始状态中启动。
  •     读取一行输入。
  •     根据输入和当前状态,转移到新状态或按适合当前状态的方式处理该行。

这个例子是关于您会遇到的最简单的情况,但它说明了我们描述过的以下模式:
Python 中一个简单的状态机输入循环

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

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

抽象状态机类

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

from

   string
  import

   upper
  class
   StateMachine

  :

  def
   __init__

  (self):
 self.handlers = {}
 self.startState = None
 self.endStates = []

  def
   add_state

  (self, name, handler, end_state=0):
 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)]

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

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

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

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

from

   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_state=1)
 m.set_start(
  "ONES")
 m.run(1)

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索python
状态机概念、概念画板ipad使用教程、python 状态机、python 有限状态机、概念画板使用教程,以便于您获取更多的相关知识。

时间: 2024-08-07 16:08:54

状态机的概念和在Python下使用状态机的教程_python的相关文章

在Gnumeric下使用Python脚本操作表格的教程_python

关于Gnumeric Gnumeric是linux平台下的一款功能强大且易于使用的电子表格软件,与其他常用电子表格软件如Excel等在风格上非常一致.Gnumeric当前的稳定版是1.2.13,对中文的支持已经比较成熟.据官方信息,Gnumeric除实现了MS Excel所有的函数外,还实现了60多个Excel中不存在的函数和基本的金融方面函数,并已经具备了高级统计分析.可扩展的随机数产生器.线性或非线性求解的计算能力.更令人惊喜的是,现在Gnumeric已经集成了Python强大的脚本编程能力

在Python的setuptools框架下生成egg的教程_python

本文介绍了 setuptools 框架的内容,它是 PEAK 的一个副项目,它提供了比 distutils 更加简单的包管理和发行功能.开始 setuptools 模块很会 "规避".例如,如果我们下载一个使用 setuptools 而不是使用 distutils 构建的包,那么安装就应该可以像我们期望的一样工作:通常使用 python setup.py install 就可以.为了实现这种功能,使用 setuptools 绑定在一起的包就会在归档文件中包含一个很小的引导模块 ez_s

Win10下Python环境搭建与配置教程_python

本教程为大家分享了win10下Python环境安装配置教程,供大家参考,具体内容如下 1.在https://www.python.org/downloads/release/python-352/网站上下载python版本 2.配置环境 右击计算机属性,然后选择高级系统设置 3.选择环境变量,配置Path输入;C:\Users\小红\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Python 3.5 4.测试python有没有安装成功

Centos5.x下升级python到python2.7版本教程_python

首先到官网下载python2.7.3版本,编译安装 复制代码 代码如下: $wget http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz $tar zxvf Python-2.7.3.tgz $cd Python-2.7.3 $./configure $make && make install 然后备份原来的python,并把python2.7做软连接到新的位置 复制代码 代码如下: $mv /usr/bin/python /us

Python程序语言快速上手教程_python

本来打算从网上找一篇入门教程,但因为Python很少是程序员的第一次接触程序所学的语言,所以网上现有的教程多不是很基础,还是决定自己写下这些. 如果没有程序基础的话,可能会觉得本文涵盖的内容有点多.对照大学里面常教的C语言的教学速度,本文大约有四五个课时的内容:对照网上程序类的视频 教程,大致相当于两三个小时的内容:对于翻一本程序书籍,大约相当于翻一个小时书.也因此,如果有深入学习的打算的话,为了效率还是推荐看书. 如果暂时不能理解本文中的一些内容也没关系,因为都是一些经常会用到的基础知识,在实

简单的Python的curses库使用教程_python

curses 库 ( ncurses ) 提供了控制字符屏幕的独立于终端的方法.curses 是大多数类似于 UNIX 的系统(包括 Linux)的标准部分,而且它已经移植到 Windows 和其它系统.curses 程序将在纯文本系统上.xterm 和其它窗口化控制台会话中运行,这使这些应用程序具有良好的可移植性.介绍 curses Python 的标准 curses 提供了"玻璃电传"(glass teletype)(在 20 世纪 70 年代,原始 curses 库刚创建时,它叫

使用Python构建Hopfield网络的教程_python

 热的东西显然会变凉.房间会会人沮丧地变得凌乱.几乎同样,消息会失真.逆转这些情况的短期策略分别是重新加热. 做卫生和使用 Hopfield 网络.本文向您介绍了三者中的最后一个,它是一个只需要特定的参数就可以消除噪声的算法.net.py 是一个特别简单的 Python 实现,将向您展示它的基本部分如何结合到一起,以及为何 Hopfield 网络有时可以自失真的图案中 重新得到原图案.尽管这个实现有局限性,不过仍然可以让您获得关于 Hopfield 网络的很多有益且有启发作用的经验. 您寻求的是

最基础的Python的socket编程入门教程_python

本文介绍使用Python进行Socket网络编程,假设读者已经具备了基本的网络编程知识和Python的基本语法知识,本文中的代码如果没有说明则都是运行在Python 3.4下. Python的socket功能封装在socket库中,要使用socket,记得先import socket,socket库的详细介绍参见官方文档.创建Socket 首先创建一个socket,使用socket库中得socket函数创建. import socket # create an INET, STREAM sock

Python实现SMTP发送邮件详细教程_python

简介 Python发送邮件的教程本人在网站搜索的时候搜索出来了一大堆,但是都是说了一大堆原理然后就推出了实现代码,我测试用给出的代码进行发送邮件时都不成功,后面找了很久才找到原因,这都是没有一个详细的环境调试导致,所以今天特出一个详细的教程,一步一步从环境调试到代码实现整一个教程,希望对还在苦苦寻找解决方法却迟迟不能得到有效解决的人员一点帮助. SMTP协议 首先了解SMTP(简单邮件传输协议),邮件传送代理程序使用SMTP协议来发送电邮到接收者的邮件服务器.SMTP协议只能用来发送邮件,不能用