• 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)
    • 1

    在我们的第一个用例中,酒店的预订专家想跟踪有宠物的客人预订的房间,以便分配额外的清洁人员。因此,每当有客人预订了带宠物的房间时,她的软件就会调用下面的函数 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]
    • 1

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

    2.Python 闭包

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

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

    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)
    • 1

    在下面的代码片段中,内部函数 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
    • 1

    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))
    • 1

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

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

    输出结果是。

    ('123''Y')
    ('313''Y')
    • 1

    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
    • 1

    我们下面的桑拿房类继承自状态机,并有一个类变量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()
    • 1

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

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

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

    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)
    • 1

    下面是上述代码的输出。正如预期的那样,我们目前处于空状态,到目前为止有两个客人使用了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]
    • 1

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

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

    本文由 mdnice 多平台发布

  • 相关阅读:
    JFLASH基本使用总结
    Java算法 每日一题(十一) 编号20:有效的括号
    python利用read_table读取dat文件案例详解
    一款值得入手的双节电池1A电流线性充电芯片-YB4028
    第二周 生活随笔——记录平凡生活中的唯一瞬间
    单个独立按键依次输入控制数据
    vue项目中 jsconfig.json是什么
    R语言ggplot2可视化:使用ggplot2可视化散点图、在geom_point参数中设置show_legend参数为FALSE配置不显示图例信息
    Vue-Router实现带参数跳转,并且保存原页面数据记录
    CSS位置偏移反爬虫绕过
  • 原文地址:https://blog.csdn.net/qq_40523298/article/details/127459548