用 Python 构建一个简单的状态机。

2025-06-07

用 Python 构建一个简单的状态机。

最初发表在我的个人博客上。

大约两年前,我发现了状态机,当时它被用于解决一个问题:将 VoIP 电话呼叫的可能状态(来电、振铃、已应答等)映射到我们可以监控的对象上。我惊讶于它的简单易用,于是决定在自己的项目中采用状态机。

我相信每个人都知道,当你发现某个新的软件原理时会发生什么;你会觉得你构建的所有东西都需要它。然而,作为负责任的开发者,我们必须扪心自问,我们尝试做的事情是否是最佳解决方案。我们必须问自己的用例是什么,在这种情况下,我们是否真的需要状态机。也许下面的示例能帮助你深入了解状态机的用途。

国家机器

在开始用 Python 实现一个简单的状态机之前,我们先快速了解一下状态机的概念和工作原理。简而言之,它本质上是一组状态和一组动作/事件。我们从一个状态开始,像图数据结构一样,根据对应边描述的条件遍历节点。我们使用该条件来找到我们想要的节点(也就是状态)。由于同一时间只有一个状态处于活动状态,因此我们可以很好地控制状态机在生命周期中的位置。这里有一个更详尽的、基于计算机科学理论的解释,可以通过视频找到如果您想了解更多,强烈建议您观看!

使用案例

状态机有很多用例,其中包括管理状态(如呼叫状态、WiFi 连接,甚至 Android 活动生命周期)或报告指标——例如用户完成登录的时间长度(登录 -> 待定 -> 成功)。

状态机尤其有趣,因为它提供了定义明确的场景,并列出了触发这些场景的条件。这使得我们能够轻松地确定边缘情况及其处理方法,因为我们被迫考虑代码中所有可能遇到的情况。

就我个人而言,理解状态机的最好方法是通过日常例子。

想象一下,你正在看着你的密码保护手机,它有两种运行状态。第一种是locked,此时你的功能有限;第二种是unlocked,此时你可以以更大的容量使用该设备。

这是上述状态机的可视化效果。

使用状态机

我们首先定义状态,这些状态被定义为状态机中的节点。在我们的例子中,我们有两个状态:locked& unlocked。在下面的示例中,我还定义了一个State对象,它将处理一些状态的实用函数(这些函数继承自该对象)。



# state.py

class State(object):
    """
    We define a state object which provides some utility functions for the
    individual states within the state machine.
    """

    def __init__(self):
        print 'Processing current state:', str(self)

    def on_event(self, event):
        """
        Handle events that are delegated to this State.
        """
        pass

    def __repr__(self):
        """
        Leverages the __str__ method to describe the State.
        """
        return self.__str__()

    def __str__(self):
        """
        Returns the name of the State.
        """
        return self.__class__.__name__


Enter fullscreen mode Exit fullscreen mode

然后可以按如下方式定义状态。



# my_states.py

from state import State

# Start of our states
class LockedState(State):
    """
    The state which indicates that there are limited device capabilities.
    """

    def on_event(self, event):
        if event == 'pin_entered':
            return UnlockedState()

        return self


class UnlockedState(State):
    """
    The state which indicates that there are no limitations on device
    capabilities.
    """

    def on_event(self, event):
        if event == 'device_locked':
            return LockedState()

        return self
# End of our states.


Enter fullscreen mode Exit fullscreen mode

然后我们定义实际的状态机。它相当简单,如下所示:



# simple_device.py

from my_states import LockedState

class SimpleDevice(object):
    """ 
    A simple state machine that mimics the functionality of a device from a 
    high level.
    """

    def __init__(self):
        """ Initialize the components. """

        # Start with a default state.
        self.state = LockedState()

    def on_event(self, event):
        """
        This is the bread and butter of the state machine. Incoming events are
        delegated to the given states which then handle the event. The result is
        then assigned as the new state.
        """

        # The next state will be the result of the on_event function.
        self.state = self.state.on_event(event)


Enter fullscreen mode Exit fullscreen mode

很简单吧?这个状态机的作用是定义一个起始状态LockedState,并暴露一个函数来处理事件。这个函数的作用是,在处理事件时,将当前状态赋值给该状态的结果。

最后,我们可以使用 python shell 测试状态机的实现。



$ python
Python 2.7.13 (default, Apr  4 2017, 08:47:57)
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> from simple_device import SimpleDevice
>>> device = SimpleDevice()
Processing current state: LockedState
>>> 
>>> device.on_event('device_locked')
>>> device.on_event('pin_entered')
Processing current state: UnlockedState
>>> 
>>> device.state
UnlockedState
>>> 
>>> device.on_event('device_locked')
Processing current state: LockedState
>>> 
>>> device.state
LockedState
>>> device.on_event('device_locked')


Enter fullscreen mode Exit fullscreen mode

您会注意到,重复的事件会被忽略,只有提供转换的事件才会被使用。当我们想要忽略事件或将一系列冗长的事件精简为一组更简单的事件时,这会是一个非常强大的工具。我最喜欢的一点是,如果将来我们想添加更多状态和转换,这非常容易,无需重写大量代码库。

结论

状态机非常棒,从需要简单状态管理的情况到指标报告,它们都已被证明非常有用且可扩展。上述技术是实现一个状态机来处理 SIP 信令事件(用于 VoIP)并测量传入事件之间的差异(以便更好地理解我们的痛点)的成果。它绝对可以扩展到几十个状态,并且可以轻松进行状态测量。

如果您正在寻找不同的解决方案,请查看 Python转换库,这是一个看起来相当有前途的状态机库。

参考
  • 在研究 Python 的状态机库时,我偶然发现了一个页面,其中记录了一个简单的实现,即上述基于示例的解决方案。原始实现请点击此处

如果您发现任何错误,或者只是想打个招呼,请告诉我!

文章来源:https://dev.to/karn/building-a-simple-state-machine-in-python
PREV
CSS 编码模式可以让你成为一名初级开发人员。
NEXT
使用幂等 API 构建弹性系统