科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道应用软件Python 中的算法和编程方法(使用状态机)

Python 中的算法和编程方法(使用状态机)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文讲解Python 中的算法和编程方法(使用状态机)

作者:Boudewijn Rempt、David Mertz 来源:code173.com  2007年9月15日

关键字: 软件

  • 评论
  • 分享微博
  • 分享邮件

处理 .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 阅读器的一般功能如下:

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


这个例子是关于您会遇到的最简单的情况,但它说明了我们描述过的以下模式:

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)

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章