ホームページ バックエンド開発 Python チュートリアル 状态机的概念和在Python下使用状态机的教程

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

Jun 10, 2016 pm 03:15 PM
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)

ログイン後にコピー

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

PHPおよびPython:さまざまなパラダイムが説明されています PHPおよびPython:さまざまなパラダイムが説明されています Apr 18, 2025 am 12:26 AM

PHPは主に手順プログラミングですが、オブジェクト指向プログラミング(OOP)もサポートしています。 Pythonは、OOP、機能、手続き上のプログラミングなど、さまざまなパラダイムをサポートしています。 PHPはWeb開発に適しており、Pythonはデータ分析や機械学習などのさまざまなアプリケーションに適しています。

PHPとPythonの選択:ガイド PHPとPythonの選択:ガイド Apr 18, 2025 am 12:24 AM

PHPはWeb開発と迅速なプロトタイピングに適しており、Pythonはデータサイエンスと機械学習に適しています。 1.PHPは、単純な構文と迅速な開発に適した動的なWeb開発に使用されます。 2。Pythonには簡潔な構文があり、複数のフィールドに適しており、強力なライブラリエコシステムがあります。

Python vs. JavaScript:学習曲線と使いやすさ Python vs. JavaScript:学習曲線と使いやすさ Apr 16, 2025 am 12:12 AM

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

PHPとPython:彼らの歴史を深く掘り下げます PHPとPython:彼らの歴史を深く掘り下げます Apr 18, 2025 am 12:25 AM

PHPは1994年に発信され、Rasmuslerdorfによって開発されました。もともとはウェブサイトの訪問者を追跡するために使用され、サーバー側のスクリプト言語に徐々に進化し、Web開発で広く使用されていました。 Pythonは、1980年代後半にGuidovan Rossumによって開発され、1991年に最初にリリースされました。コードの読みやすさとシンプルさを強調し、科学的コンピューティング、データ分析、その他の分野に適しています。

Windows 8でコードを実行できます Windows 8でコードを実行できます Apr 15, 2025 pm 07:24 PM

VSコードはWindows 8で実行できますが、エクスペリエンスは大きくない場合があります。まず、システムが最新のパッチに更新されていることを確認してから、システムアーキテクチャに一致するVSコードインストールパッケージをダウンロードして、プロンプトとしてインストールします。インストール後、一部の拡張機能はWindows 8と互換性があり、代替拡張機能を探すか、仮想マシンで新しいWindowsシステムを使用する必要があることに注意してください。必要な拡張機能をインストールして、適切に動作するかどうかを確認します。 Windows 8ではVSコードは実行可能ですが、開発エクスペリエンスとセキュリティを向上させるために、新しいWindowsシステムにアップグレードすることをお勧めします。

Visual StudioコードはPythonで使用できますか Visual StudioコードはPythonで使用できますか Apr 15, 2025 pm 08:18 PM

VSコードはPythonの書き込みに使用でき、Pythonアプリケーションを開発するための理想的なツールになる多くの機能を提供できます。ユーザーは以下を可能にします。Python拡張機能をインストールして、コードの完了、構文の強調表示、デバッグなどの関数を取得できます。デバッガーを使用して、コードを段階的に追跡し、エラーを見つけて修正します。バージョンコントロールのためにGitを統合します。コードフォーマットツールを使用して、コードの一貫性を維持します。糸くずツールを使用して、事前に潜在的な問題を発見します。

ターミナルVSCODEでプログラムを実行する方法 ターミナルVSCODEでプログラムを実行する方法 Apr 15, 2025 pm 06:42 PM

VSコードでは、次の手順を通じて端末でプログラムを実行できます。コードを準備し、統合端子を開き、コードディレクトリが端末作業ディレクトリと一致していることを確認します。プログラミング言語(pythonのpython your_file_name.pyなど)に従って実行コマンドを選択して、それが正常に実行されるかどうかを確認し、エラーを解決します。デバッガーを使用して、デバッグ効率を向上させます。

VSCODE拡張機能は悪意がありますか? VSCODE拡張機能は悪意がありますか? Apr 15, 2025 pm 07:57 PM

VSコード拡張機能は、悪意のあるコードの隠れ、脆弱性の活用、合法的な拡張機能としての自慰行為など、悪意のあるリスクを引き起こします。悪意のある拡張機能を識別する方法には、パブリッシャーのチェック、コメントの読み取り、コードのチェック、およびインストールに注意してください。セキュリティ対策には、セキュリティ認識、良好な習慣、定期的な更新、ウイルス対策ソフトウェアも含まれます。

See all articles