在《编写高质量代码:改善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里面,两个状态类不用关心状态怎么转换,但是仍然还有以下缺陷(基本摘自书中):
- 查询对象的当前状态很麻烦
- 状态切换时如果需要对原状态做一些清理工作,对新的状态做一些初始化工作,那把这个清理和初始化工作都都写在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的实例。解读完毕。