当前位置:   article > 正文

建议53:用状态模式美化代码,关于python-state工具包的理解_python state

python state
    在《编写高质量代码:改善python程序的91个建议》的 建议53:用状态模式美化代码小节中,介绍了状态模式如下:就是当 一个对象的内在状态改变时,允许改变其行为,但这个对象看起来像是改变了其类。
    正如: http://blog.csdn.net/ponder008/article/details/6887443博文所写代码,

#encoding=utf-8
#
#by panda
#状态模式

def printInfo(info):
    print unicode(info, 'utf-8').encode('gbk')

#State:上班状态基类
class State():
    def WriteProgram():
        pass

#上午工作状态类
class ForenoonState(State):
    def WriteProgram(self,w):
        if (w.Hour < 12):
            printInfo("当前时间:%d点 工作状态:上午工作,精神百倍" % w.Hour)
        else:
            w.SetState(noonState())
            w.WriteProgram()            

#中午工作状态类
class noonState(State):
    def WriteProgram(self,w):
        if (w.Hour < 13):
            printInfo("当前时间:%d点 午饭;午休" % w.Hour)
        else:
            w.SetState(AfternoonState())
            w.WriteProgram();

#下午工作状态类
class AfternoonState(State):
    def WriteProgram(self,w):
        if (w.Hour < 18):
            printInfo("当前时间:%d点 下午状态还不错,继续努力" % w.Hour)
        else:
            w.SetState(EveningState())
            w.WriteProgram();

#晚上工作状态类
class EveningState(State):
    def WriteProgram(self,w):
        if(w.TaskFinished):
            w.SetState(RestState())
            w.WriteProgram()
            return

        if (w.Hour < 21):
            printInfo("当前时间:%d点 加班哦,好累!" % w.Hour)
        else:
            w.SetState(SleepingState())
            w.WriteProgram();

#睡眠状态
class SleepingState(State):
    def WriteProgram(self,w):
        printInfo("当前时间:%d点 睡觉了" % w.Hour)

#下班工作状态
class RestState(State):
    def WriteProgram(self,w):
        printInfo("当前时间:%d点 下班回家了" % w.Hour)

#Context:上班
class Work():
    state = ForenoonState();
    TaskFinished = False
    Hour = 8.0

    def SetState(self, state):
        self.state = state

    def WriteProgram(self):
        self.state.WriteProgram(self)

def clientUI():
    work = Work()    
    for i in range(9,23,1):
        work.Hour = i
        if(i > 19):
            work.TaskFinished = True
        work.WriteProgram()
    return

if __name__ == '__main__':
    clientUI() ;

运行结果:
>>> 
当前时间:9点 工作状态:上午工作,精神百倍
当前时间:10点 工作状态:上午工作,精神百倍
当前时间:11点 工作状态:上午工作,精神百倍
当前时间:12点 午饭;午休
当前时间:13点 下午状态还不错,继续努力
当前时间:14点 下午状态还不错,继续努力
当前时间:15点 下午状态还不错,继续努力
当前时间:16点 下午状态还不错,继续努力
当前时间:17点 下午状态还不错,继续努力
当前时间:18点 加班哦,好累!
当前时间:19点 加班哦,好累!
当前时间:20点 下班回家了
当前时间:21点 下班回家了
当前时间:22点 下班回家了

    这种状态模式,逻辑控制部分和状态转换控制都放在了不同的状态类中,但是如果我们希望将所有的逻辑控制和状态转换都放在同一个地方,而状态类只需要关注自己要做的事情即可,就出现了书中的示例代码:

def workday():
    print 'work hard!'

def weekday():
    print 'play harder!'

class People(object):pass

people = People()

for i in xrange(1,8):
    if i == 6:
        people.day = weekday
    if i == 1:
        people.day =workday
    people.day()

    解释:当我第一眼看最后一行代码的时候,觉得people.day()没定义啊,当我从for开始往下看的时候,才醒悟,汗!。当i=1,day的状态为workday,然后直到i=6才会改变状态为weekday,也就是说,i的值在1~5时,状态一直是workday,到了6才是weekday,当然7也是weekday。
    好了,现在所有的逻辑控制部分都在for里面,两个状态类不用关心状态怎么转换,但是仍然还有以下缺陷(基本摘自书中):
  1.     查询对象的当前状态很麻烦
  2.     状态切换时如果需要对原状态做一些清理工作,对新的状态做一些初始化工作,那把这个清理和初始化工作都都写在for里面或者原来的状态类里,必然有重复,因为每个状态都要进行初始化和清理,那我几个状态转换下来,这个for循环已经没法保持好看的身材了。我们需要一个机制来简化这个问题。
    PS:其实这些问题只是在状态类较多的情况下更加明显,如果只是两到三个状态类,个人意见是随便写,重复两三条没啥问题(也许是自己要求太低。。)
    好了,言归正传,如果状态类很多,多到要写状态初始化和清理都很烦的时候,那我们急需一个辅助工具来做这个重复又头疼的事情,python-state工具通过几个辅助函数和修饰函数解决了这个问题,并定义了一个简明状态机框架(这个真没看出来,汗!)。

    地址: https://pypi.python.org/pypi/state/0.1.2dev-r2可以下载,也可以通过pip install state直接安装。当时看这个包其实代码量很少,于是没有安装,直接贴在了代码上面,哈哈。。

# -*- coding:utf-8 -*-
import inspect
import functools

class State(object):
    @staticmethod
    def __begin__(host):
        pass

    @staticmethod
    def __end__(host):
        pass

def stateful(cls):
    defaults = []
    for i in cls.__dict__.itervalues():
        if inspect.isclass(i) and issubclass(i, State) and hasattr(i, 'default') and i.default:
            defaults.append(i)
    if not defaults:
        raise Error('%s\'s default state is not found.' % cls.__name__)
    if len(defaults) > 1:
        raise Error('%s\'s has too much default state.%s' % (cls.__name__, defaults))
    default = defaults[0]

    old__init__ = cls.__init__
    if hasattr(cls, '__getattr__'):
        old__getattr__ = getattr(cls, '__getattr__')
    else:
        old__getattr__ = getattr(cls, '__getattribute__')

    def __init__(self, *a, **kw):
        self.__state__ = default
        self.__state__.__begin__(self)
        return old__init__(self, *a, **kw)

    def __getattr__(self, name):
        try:
            old__getattr__(self, name)
        except AttributeError, e:
            pass
        try:
            f = getattr(curr(self), name)
        except AttributeError:
            raise e
        if not callable(f):
            raise e
        return functools.partial(f, self)

    cls.__init__ = __init__
    cls.__getattr__ = __getattr__
    return cls

def curr(host):
    return host.__state__

def switch(host, new_state):
    host.__state__.__end__(host)
    host.__state__ = new_state
    new_state.__begin__(host)

behavior = staticmethod
#上面是state工具的代码,下面是书中的使用示例
@stateful
class People(object):
        class Workday(State):
                default = True
                @behavior
                def day(self):
                        print 'word hard!'
        class Weekday(State):
                @behavior
                def day(self):
                        print 'play harder!'
people = People()
for i in xrange(1,8):
        if i == 6:
                switch(people,People.Weekday)
        if i == 1:
                switch(people,People.Workday)
        people.day()

运行下:
[ wxx@eb181 worktime]$ ./state_test.py 
word hard!
word hard!
word hard!
word hard!
word hard!
play harder!
play harder!

    按照个人理解解读下这个工具以及使用示例,首先State类是让各个状态类继承的,定义了两个静态方法,宿主类(此例为People)重载这两个方法就可以实现初始化和清理工作;接下来是stateful(cls)函数,这是个装饰函数,cls.__dict__.itervalues()列出了所有宿主类的属性,inspect.isclass(i),判断i是否是类,issubclass(i,State)判断是不是State的子类,hasattr(i,'default'),判断是不是默认属性(People中定义default中为True的为默认状态),最终defaults列表中只有Workday状态类,cls.__init__调用People默认的初始化方法,下面一个if hasattr判断People中是否有__getattr__,显然People类中没有明显的重载__getattr__方法(在People类中有个默认的__getattr__),所以,执行了else部分,初始化部分初始默认状态,完成默认状态的初始化,即__begin__方法,然后顺便将People类也初始化,核心是重载了__getattr__()方法,查询People类的属性和方法,这里的name值为people类中day,最终cls初始化和__getattr__被重载,返回cls。这里要注意,People类的day是静态方法的self参数只是为了理解状态类是的宿主是People的实例。后面的curr方法查询当前状态,switch方法用于切换状态。(PS:个人感觉getattr那里理解的还有问题。。)
    为了理解getattr方法,懒得自己写,找了网上的代码:
>>> li  =  "Larry" "Curly"  ]
>>> li.pop
<built - in  method pop of  list  object  at  0xb76b364c >
>>>  getattr ( li,  "pop"  )
<built - in  method pop of  list  object  at  0xb76b364c >
>>>  getattr ( li,  "append"  )(  "Moe"  )
>>> li
[ 'Larry' 'Curly' 'Moe' ]
>>> 

    从上面的代码可以看出li.pop 等用于 getattr( li, "pop" ),但是这样不是调用pop函数,真正的

的调用是getattr( li, "append" )("Moe")。

    回到我们的stateful代码,old__getattr__ = getattr(cls,'__getattribute__'),当__getattr__中传来name参数,就相当于执行了cls.__getattribute__.name,如果查找name属性失败,pass这个异常,继续进行getattr(curr(self),name),这里curr(host)返回的是Workday类和Weekday类,name也是day,然后检查f是否是callable的,然后return f的偏函数,传入一个参数self,最后替换cls里的__init__和__getattr__,返回cls。
这个转换状态的方法,首先执行上一个状态的清理工作,也就是__end__,然后指定新的状态,然后完成初始化。
 
我们看示例中的day方法,是个静态方法,为什么有self参数?其实首先self不是python的关键字,这个self只是为了帮助理解状态类宿主是People的实例。解读完毕。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/524420
推荐阅读
相关标签
  

闽ICP备14008679号