Python中记住过去(模型状态)的五种方法

在Python中记住过去(模型状态)的五种方法 从封闭函数和迭代器到状态机Python库

有人说...

"那些不能记住过去的人,注定要重复它"。G. Santayana, 1905

就像在现实生活中一样,在软件中,重要的是要知道 "状态 "是如何保存的,这可能是理想的行为或不是。状态是一个动态系统在某一时期的行为。

在软件中,系统的状态是通过保留一组相关变量的值来建模的。在一个更复杂的形式中,软件中的状态是通过状态机来建模的,其特征是状态、过渡,通常还有从一个状态过渡到另一个状态的概率。机器学习中的有限状态机(简称状态机)的例子是马尔科夫链和马尔科夫模型[1]。状态机在各种应用中都很重要。

安全关键型系统。例如,医疗设备[2]、配电系统[3]和运输系统,如铁路[4]、飞机[5]和自动驾驶汽车[6]。

区块链数据传输、存储和检索系统。区块链也被称为无限状态机[7]。

视频游戏[8],对话式人工智能[9],图形用户界面的实现[10]。

用BLAST(Basic Local Alignment Search Tool)寻找蛋白质序列的相似性[11]。

用于时间序列预测的深度状态空间模型[12]。

每个软件开发者都知道,要想在不同的函数调用之间保留一个变量的状态,最简单(也是最丑陋)的方法就是将这个变量声明为一个全局变量。但是,让我们忘掉丑陋,在管理一个虚构的、风景如画的地中海酒店的客人的用例中,描述五种不同的方法来模拟 Python 的状态。我们的酒店叫做Artemis Cypress(Artemis是古希腊的女神,而柏树是她的圣树)。

在创建管理酒店客人的功能时,我们将研究以下在 Python 中管理状态的方法。

空列表作为默认的函数参数。

Python闭包

迭代器

类变量

状态机Python库

1、默认函数参数的空列表

在我们讨论第一个用例之前,让我们注意以下几点。一般来说,将空列表作为默认函数参数是个坏主意。我们将在下面的用例中解释原因。

def roomsWithPet(roomNumber,lr=[]):
    lr.append(roomNumber)
    print(f"The rooms with pets are:  {lr}")
    return lr
    
L=roomsWithPet(300)
L=roomsWithPet(101)
L=roomsWithPet(200)

在我们的第一个用例中,酒店的预订专家想跟踪有宠物的客人预订的房间,以便分配额外的清洁人员。因此,每当有客人预订了带宠物的房间时,她的软件就会调用下面的函数 roomsWithPet()

该函数需要一个位置参数(roomNumber)和一个命名参数lr,其默认值为一个空列表。这个函数被调用了三次,只有roomNumber这个参数。

lr发生了什么? 在函数第一次被调用时,lr被创建并填充了roomNumber的值,随后的每一次,新的roomNumber值都被附加到第一次调用时创建的lr列表上。

因此,输出是:

The rooms with pets are:  [300]
The rooms with pets are:  [300, 101]
The rooms with pets are:  [300, 101, 200]

在我们的例子中,这是理想的行为,但在许多情况下,你想在每次函数调用时都创建一个新的列表,这就不是了。因此,重要的是要知道,把一个空列表作为默认参数传递给一个函数,在函数调用之间保留列表的状态。

2.Python 闭包

在我们的第二个用例中,四个在酒店参加会议的客人来到前台办理退房手续。这些客人是同一家公司的员工,在酒店共用一个大套房。接待员需要计算每个人的应付余额。她可以调用下面的函数,输入的参数是套房号(501)、入住天数(3)、套房价格/天(400美元),以及每个人的额外费用(在酒店餐厅用餐)。但是调用一个函数四次,每次只有最后一个参数不同,这有点麻烦。

def checkOut(roomN,rate,nights,extra):
    amountOwned=rate*nights+extra
    return amountOwned

Python闭包为我们提供了一种更优雅的方式来做到这一点。它们是内部函数,可以访问外部函数中的值,甚至在外部函数执行完毕之后[13]。换句话说,闭包函数有能力记住外层函数的值,即使这些值已经不在内存中了[14]。总的来说,当我们不想写一个类时,Python闭包提供了一个很好的方法来进行数据隐藏,这是实现数据隐藏最常见的方法。

def balanceOwned(roomN,rate,nights):
    def increaseByMeals(extra):
        amountOwned=rate*nights+extra
        print(f"Dear Guest of Room {roomN}, you have"
        "a due balance:""${:.2f}".format(amountOwned))
        return amountOwned
    return increaseByMeals
    
ba = balanceOwned(201,400,3)
ba(200)
ba(150)
ba(180)
ba(190)

在下面的代码片段中,内部函数 increaseByMeals() 是一个闭包函数,因为它记住了外部函数 balanceOwned() 的值,即使在后者执行之后。因此,这使得我们可以写出以下代码,其中balanceOwned()只被调用一次,并在其执行后,我们用每个客人的餐费调用它四次。

更加优雅了。现在的输出是。

Dear Guest of Room 201, you have a due balance: $1400.00
Dear Guest of Room 201, you have a due balance: $1350.00
Dear Guest of Room 201, you have a due balance: $1380.00
Dear Guest of Room 201, you have a due balance: $1390.00

3.迭代器

在我们的第三个用例中,酒店的客人可以预约瑜伽教练或体育教练的个人课程。在下面的代码中,D是一个字典,键是房间号,值是 "Y "或 "PT"("Y "表示要求的瑜伽教练,"PT "表示要求的身体教练)。

D = {
    
    "123":"Y","111":"PT","313":"Y","112":"Y","201":"PT"}
ff = filter(lambda e:e[1]=="Y", D.items())
print(next(ff))
print(next(ff))

每天早上,一个新的字典被创建,其中房间号是根据请求的时间输入的。然后,为了得到请求瑜伽的房间,执行随后的 filter() 命令。现在中央服务器已经为瑜伽教练准备好了。

每次瑜伽教练要求去下一个房间时,服务器只需调用 next(ff),就会返回下一个排队等候瑜伽教练的房间。这样一来,瑜伽教练就不需要就下一个房间的去向相互沟通。一个简单的迭代器保留了房间如何被服务的整体状态,而next()命令将我们带到下一个需要被服务的房间。

输出结果是。

('123''Y')
('313''Y')

4.类变量和状态机Python库

在本节中,我们将讨论上述在Python中保留状态的最后两种方法。

类变量。这些是在类的结构中定义的变量,在类被构造时声明,并由类的所有实例共享。如果一个类的实例改变了一个类的变量,那么该类的所有实例都会受到影响,通过这种方式,类的变量在类的实例之间保留它们的状态。

Python 状态机库,是一个开源的库[15],允许定义状态和从状态到状态的转换动作。

我们将在最后一个用例中展示类变量和Python状态机库的使用,这个用例为我们酒店的桑拿房的使用提供了模型。酒店在两个不同的地方有两个桑拿房,每个都可以容纳两个人。

class Sauna(StateMachine):
    #Class variable that counts the number of class instances
    inst_counter=0
    def __init__(self):
       super().__init__()
       self.entryTimes=[]
       self.guestcount=0
       self.__roomnumbers=[]
       Sauna.inst_counter+=1
    
    #States
    empty = State('Empty', initial=True)
    space_avail = State('OneSpaceOccupied')
    full = State('NoSpace')
    
    #Transitions
    occupied = empty.to(space_avail)
    guestleft = space_avail.to(empty)
    nospace = space_avail.to(full)
    spacefortwo = full.to(empty)
    nomorefull = full.to(space_avail)
    
   
    #Actions on Transitions
    def on_enter_empty(self):
        print("Welcome to the Sauna Room!")
        
    def on_enter_space_avail(self):
        print("One sauna seat available!")
        currentT = DT.now()
        self.__roomnumbers.append(random.randint(1,30))
        self.entryTimes.append(currentT.strftime("%Y-%m-%d %H:%M:%S"))
        self.guestcount+=1
        
    def on_enter_full(self):
        print("Sorry, the Sauna Room is full!")
        self.__roomnumbers.append(random.randint(1, 30))
        currentT = DT.now()
        self.entryTimes.append(currentT.strftime("%Y-%m-%d %H:%M:%S"))
        self.guestcount+=1
        
    def on_enter_full(self):
        print("Sorry, the Sauna Room is full!")
        self.__roomnumbers.append(random.randint(1, 30))
        currentT = DT.now()
        self.entryTimes.append(currentT.strftime("%Y-%m-%d %H:%M:%S"))
        self.guestcount+=1
        
    def getEntryTimes(self):
        return self.entryTimes
    
    def getGuestCount(self):
        return self.guestcount
    
    @classmethod
    def getSaunaCount(cls):
        return Sauna.inst_counter
          
    @property
    def getroomnumbers(self):
        return self.__roomnumbers
    
    
    cycle= occupied | nospace | spacefortwo

我们下面的桑拿房类继承自状态机,并有一个类变量inst_counter,用于计算该类的实例数量,每次调用该类的构造函数时都会递增。我们为每个桑拿房定义了三种状态:空(0个客人)、空闲(一个客人)和满(两个客人)。然后,我们给可能的过渡起名字。例如,nospace意味着我们已经从space_avail状态过渡到full状态。

在定义了过渡之后,我们定义了过渡的动作。例如,函数on_enter_space_avail()实现了当一个客人进入先前空的桑拿房时的动作。除了打印出有一个桑拿房座位可用外,该函数还增加实例变量guestcount。工作人员想知道每个房间有多少客人使用过,以评估特定桑拿房位置的受欢迎程度。

获取当前时间。然后将其追加到列表entryTimes中,这是一个实例变量。知道进入的时间是很有用的,这样在繁忙的时候,工作人员可以安排额外的毛巾等。

记录正在使用该房间的客人的房间号,将其追加到列表roomnumbers中。这是为了计费的目的(超过两个疗程/周,这是免费的,客人要为额外的疗程计费)。

在我们的案例中,房间号是随机生成的。另外,值得注意的是,roomnumbers列表是类的一个私有成员,为了访问它,我们定义了getroomnumbers这个属性。

最后,我们定义了循环,它是一个状态的管道(序列):已被占用、无空间、空间二。

下面我们创建一个桑拿房的实例,并通过调用cycle()进行状态转换。

ArtemisSauna1 = Sauna()
ArtemisSauna1.cycle()
ArtemisSauna1.cycle()
ArtemisSauna1.cycle()

输出显示如下。在第一次调用cycle()后,它处于被占用的状态,所以它打印出 "有一个桑拿座位"。在第二次调用后,它处于nospace状态,并打印出 "对不起,桑拿房已满"。在第三次调用后,它处于spacefortwo状态,并打印出 "欢迎来到桑拿房!"。

One sauna seat available!
Sorry, the Sauna Room is full!
Welcome to the Sauna Room!

现在,让我们得到一些额外的信息:客人的进入时间、当前状态、到目前为止的客人数量和房间号。

L=ArtemisSauna1.getEntryTimes()
print(L)
print(ArtemisSauna1.current_state)
c=print(f"The number of guests is: {ArtemisSauna1.getGuestCount()}")
ic=print(f"The number of used saunas is: {ArtemisSauna1.getSaunaCount()}")
L3=ArtemisSauna1.getroomnumbers
print(L3)

下面是上述代码的输出。正如预期的那样,我们目前处于空状态,到目前为止有两个客人使用了ArtemisSauna1,而且我们目前有一个Sauna类的实例。

['2022-06-10 14:46:35''2022-06-10 14:46:35']
State('Empty', identifier='empty', value='empty', initial=True)
The number of guests is: 2
The number of used saunas is: 1
[28, 26]

Python 提供了许多保留状态的方法,或者换句话说,保持对进程的跟踪。这很有价值,特别是对于安全关键系统,软件同时跟踪许多物理过程的状态,并将状态变化映射到适当的行动。

另一方面,正如许多GUI(图形用户界面)开发者所证实的那样,最恼人的缺陷之一是GUI不响应用户的行动,而是被锁定在一个特定的状态。总之,无论我们的目标是否是保留状态,我们都需要注意Python的保留状态的特异性。

本文由 mdnice 多平台发布

猜你喜欢

转载自blog.csdn.net/qq_40523298/article/details/127459548