建议53:用状态模式美化代码,关于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的keyword,这个self仅仅是为了帮助理解状态类宿主是People的实例。解读完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值