- 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章 性能剖析与优化
建议51:用 mixin 模式让程序更加灵活
在理解mixin之前,有必要先重温一下模板方法模式。所谓的模板方法模式就是在一个方法中定义一个算法的骨架,并将一些实现步骤延迟到子类中。模板方法可以使子类在不改变算法结构的情况下,重新定义算法中的某些步骤。在这里,算法也可以理解为行为。
模板方法模式在C++或其他语言中并无不妥,但是在Python语言中,则颇有点画蛇添足的味道。比如模板方法,需要先定义一个基类,而实现行为的某些步骤则必须在其子类中,在Python中并无必要。
class People(object): def make_tea(self): teapot = self.get_teapot() teapot.put_in_tea() teapot.put_in_water() return teapot
在这个例子中,get_teapot()方法并不需要预先定义。假设在上班时,使用的是简易茶壶,而在家里,使用的是功夫茶壶,那么可以这样编写代码:
class OfficePeople(People): def get_teapot(self): return SimpleTeapot() class HomePeople(People): def get_teapot(self): return KungfuTeapot()
这段代码工作得很好,虽然看起来像模板方法,但是基类并不需要预先声明抽象方法,甚至还带来调试代码的便利。假定存在一个People的子类StreetPeople,用以描述“正走在街上的人”,作为“没有人会随身携带茶壶”的常识的反映,这个类将不会实现get_teapot()方法,所以一调用make_tea()就会产生一个找不到get_teapot()方法的AttributeError。由此程序员马上会想到“正走在街上的人”边走边泡茶这样的需求是不合理的,从而能够在更高层次上考虑业务的合理性,在更接近本源的地方修正错误。
但是,这段代码并不完美。老板(OfficePeople的一个实例)拥有巨大的办公室,他购置了功夫茶具,他要在办公室喝功夫茶了。怎么办?答案有两种,一种是从OfficePeople继承子类Boss,重写它的get_teapot(),使它返回功夫茶具;另一个则是把get_teapot()方法提取出来,把它以多继承的方式做一次静态混入。
class UseSimpleTeapot(object): def get_teapot(self): return SimpleTeapot() class UseKungfuTeapot(object): def get_teapot(self): return KungfuTeapot() class OfficePeople(People, UseSimpleTeapot):pass class HomePeople(People, UseKungfuTeapot):pass class Boss(People, UseKungfuTeapot):pass
这样就很好地解决了老板在办公室也要喝功夫茶的需求。但是这样的代码仍然没有把Python的动态性表现出来:当新的需求出现时,需要更改类定义。比如随着公司扩张,越来越多的人入职,OffiecPeople的需求越来越多,开始出现有人不喝茶而是喝咖啡,也有人既喜欢喝茶还喜欢喝咖啡,出现了喜欢在独立办公室抽雪茄的职业经理人……这些类越来越多,代码越发难以维护。让我们开始寄望于动态地生成不同的实例。
def simple_tea_people(): people = People() people.__bases__ += (UseSimpleTeapot,) return people def coffee_people(): people = People() people.__bases__ += (UseCoffeepot,) return people def tea_and_coffee_people(): people = People() people.__bases__ += (UseSimpleTeapot, UseCoffeepot,) return people def boss(): people = People() people.__bases__ += (KungfuTeapot, UseCoffeepot,) return people
这个代码能够运行的原理是,每个类都有一个__bases__属性,它是一个元组,用来存放所有的基类。与其他静态语言不同,Python语言中的基类在运行中可以动态改变。所以当我们向其中增加新的基类时,这个类就拥有了新的方法,也就是所谓的混入(mixin)。这种动态性的好处在于代码获得了更丰富的扩展功能。想象一下,你之前写好的代码并不需要个性,只要后期为它增加基类,就能够增强功能(或替换原有行为),这多么方便!值得进一步探索的是,利用反射技术,甚至不需要修改代码。假定我们在OA系统里定义员工的时候,有一个特性选择页面,在里面可以勾选该员工的需求。比如对于Boss,可以勾选功夫茶和咖啡,那么通过的代码可能如下:
import mixins def staff(): people = People(): bases = [] for i in config.checked(); bases.append(getattr(mixins, i)) people.__bases__ += tuple(bases) return people
看,通过这个框架代码,OA系统的开发人员只需要把员工常见的需求定义成Mixin预告放在mixins模块中,就可以在不修改代码的情况下通过管理界面满足几乎所有需求了。Python的动态性优势也在这个例子中得到了很好的展现。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论