- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议53:用状态模式美化代码
所谓状态模式,就是当一个对象的内在状态改变时允许改变其行为,但这个对象看起来像是改变了其类。状态模式主要用于控制一个对象状态的条件表达式过于复杂的情况,其可把状态的判断逻辑转移到表示不同状态的一系列类中,进而把复杂的判断逻辑简化。
得益于Python语言的动态性,状态模式的Python实现与C++等语言的版本比起来简单得多。举个例子,一个人,工作日和周日的日常生活是不同的。
def workday(): print 'work hard!' def weekend(): print 'play harder!' class People(object):pass people = People() while True: for i in xrange(1, 8): if i == 6: people.day = weekend if i == 1: people.day = workday people.day()
运行上述代码,输出如下:
work hard! work hard! work hard! work hard! work hard! play harder! play harder! ...
就这样,通过在不同的条件下将实例的方法(即行为)替换掉,就实现了状态模式。但是这个简单的例子仍然有以下缺陷:
查询对象的当前状态很麻烦。
状态切换时需要对原状态做一些清扫工作,而对新的状态需要做一些初始化工作,因为每个状态需要做的事情不同,全部写在切换状态的代码中必然重复,所以需要一个机制来简化。
python-state包通过几个辅助函数和修饰函数很好地解决了这个问题,并且定义了一个简明状态机框架。先用pip安装它。
pip install state
然后用它改写之前的例子。
from state import curr, switch, stateful,State, behavior @stateful class People(object): class Workday(State): default = True @behavior def day(self): print 'work hard.' class Weekend(State): @behavior def day(self): print 'play harder!' people = People() while True: for i in xrange(1, 8): if i == 6: switch(people, People.Weekend) if i == 1: switch(people, People.Workday) people.day()
怎么样?是不是感觉好像比应用模式之前的代码还要长?这是因为例子太简单了,后面再给大家展示更贴近真实业务需求的例子。现在我们先按下这个不表,单看最后一行的people.day()。people是People的一个实例,但是People并没有定义day()方法啊?为了解决这个疑惑,需要我们从头看起。
首先是@stateful这个修饰函数,它包含了许多“黑魔法”,其中最重要的是重载了被修饰类的__getattr__()方法从而使得People的实例能够调用当前状态类的方法。被@stateful修饰后的类的实例是带有状态的,能够使用curr()查询当前状态,也可以使用switch()进行状态切换。接下来继续往下看,可以看到类Workday继续自State类,这个State类也是来自于state包,从其派生的子类能够使用__begin__和__end__状态转换协议,通过重载这两个协议,子类能够自定义进入和离开当前状态时对宿主(在本例中即people)的初始化和清理工作。对于一个@stateful类而言,有一个默认的状态(即其实例初始化后的第一个状态),通过类定义的default属性标识,default设置为True的类成为默认状态。@behavior修饰函数用以修饰状态类的方法,其实它是内置函数staticmethod的别名。为什么要将状态类的方法实现为静态方法呢?因为state包的原则是状态类只有行为,没有状态(状态都保存在宿主上),这样可以更好地实现代码重用。那么day()方法既然是静态的,为什么有self参数?这其实是因为self并不是Python的关键字,在这里使用self有助于理解状态类的宿主是People的实例。
至此,读者对state这个简单的包就基本上了解清楚了。下面讲一个来自真实业务的例子,看它如何美化原有的代码。在网络编程中,通常有一个User类,每一个在线用户都有一个User的实例与之对应。User有一些方法,需要确保用户登录之后才能调用,比如查看用户信息。这些方法大概像这样:
class User(object): def signin(self, usr, pwd): ... self._signin = True def do_sth(self, *a, **kw): if not self._signin: raise NeedSignin() ...
真实项目中,类似do_sth()的业务代码数量不少,如果每个函数前两行都是if…raise…,以确保调用场景正确,那么可以想象得出来代码该有多么难看(代码一重复就不好看了)。这时候程序员会选择使用decorator来修饰这些业务代码。
def ensure_signin(func): def _func(self, *a, **kw): if not self._signin: raise NeedSignin() return func(self, *a, **kw) return _func @ensure_signin def do_sth(self, *a, **kw): ...
上述代码看上去很完美的解决方案,而且@ensure_signin相当Pythonic。但是想象一下,某些地方,你除了要确定登录之外,还需要确定是否在战斗副本中,角色是否已经死亡……等等。想象一下,十个八个方法,每个方法上面都顶着四五个修饰函数,该有多么丑陋!这就是状态模式可以美化的地方。
@stateful class User(object): class NeedSignin(State): default = True @behavior def signin(self, usr, pwd): ... switch(self, Player.Signin) class Signin(State): @behavior def move(self, dst): ... @behavior def atk(self, other): ...
可以看到,当用户登录以后,就切换到了Player.Signin状态,而在Signin状态的行为是不需要做是否已经登录的判断的,这是因为除了登录成功,User的实例无法跳转到Signin状态,反过来说就是只要当前状态是Signin,那必定已经登录,自然无须再验证。
可以看到,通过状态模式,可以像decorator一样去掉if…raise…上下文判断,但比它更棒的是真的一个if…raise…都没有了。另外,需要多重判断的时候要给一个方法戴上四五顶“帽子”的情况也没有了,还通过把多个方法分派到不同的状态类,消灭掉一般情况下Player总是一个巨类的“坏味道”,保持类的短小,更容易维护和重用。不过这些都比不上一个更大的好处:当调用当前状态不存在的行为时,出错信息抛出的是AttributeError,从而避免把问题变为复杂的逻辑错误,让程序员更容易找到出错位置,进而修正问题。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论