当前位置: 首页 > news >正文

Python Cookbook-6.17 NuIl对象设计模式的实现

任务

你想减少代码中的条件声明,尤其是针对特殊情况的检查。

解决方案

一种常见的代表“这里什么也没有”的占位符是 None,但我们还可以定义一个类,其行为方式和这种占位符相似,而且效果更好:

class Null(object):'''Null对象总是很可靠地什么也不做'''#可选的优化:确保每个子类只有一个实例#(完全是为了节省内存,功能上没有任何差异)def __new__(cls,*args,**kwargs):if '_inst' not in vars(cls):cls._inst = type.__new__(cls,*args,**kwargs)return cls._instdef __init__(self,*args,**kwars):passdef __call__(self,*args,**kwarqs):return selfdef __repr_(self):return "Null()"def __nonzero__(self):return Falsedef __getattr__(self,name):return selfdef __setattr__(self,name,value):return selfdef __delattr__(self,name):return self

讨论

可以使用 Null 类的一个实例而不是原生的 None。使用这种实例作为占位符,而不是None,就可以在你的代码中避免很多条件声明,而且能够用极少的特殊值检查来实现算法。本节解决方案是Null对象设计模式的一个实现。(参考B.Woolf,Paltern Languageso/Programming中的“The Null Object Pattern”[PLoP 96,1996年9月]。)

本节中的Null类忽略了所有在构建时和调用实例时传入的参数,也忽略了所有的设置和删除属性的操作。任何调用或者访问属性(或者方法,因为Python并不区分两者,一概调用__getattr__)的操作都返回同一个Null实例(即 self——没有什么必要创建一个新实例)。比如,假如你有这样的一个计算:

def compute(x,y):try:lots of computation here to return some appropriate objectexcept SomeError:return None

而你这样用它:

for x in xs:for y in ys:obj = compute(x,y)if obj is not None:obj.somemethod(y,x)

可以将计算修改为:

def compute(x,y):try:lots of computation here to return some appropriate objectexcept SomeError:return Null()

对它的用法可以简化为:

for x in xs:for y in ys:compute(x,y).somemethod(y,x)

其要点是你无须检查 compute 究竟是返回了一个真实的结果还是Null的一个实例:即使是后者,也可以安全无害地调用它的任何方法。下面是另一个更具体的使用例子:

log = err =Null()
if verbose:log = open('/tmp/log','w')err = open('/tmp/err','W')
log.write('blabla')
err.write('blabla error')

很明显可以避免某种代码“污染",如if verbose:这样的防护性代码,总是散布于各处可以直接调用 log.write(‘bla’),无须用以往的表达方式,如if log is not None:log.write(‘bla’)。

在新的对象模型中,对于执行某些操作所需要的特殊方法,Python 并不会对实例调用__getattr__方法(而是检查该实例的类的槽(sot))。需要谨慎地定制 Null 类来满足你的应用对空对象操作的需求,因此需要仔细设计空对象类的特殊方法,不管它们是直接嵌入基类的代码中的,还是以子类化的方式扩展的。举个例子,对于本节解决方案中的 Null,你无法索引 Null 实例,也不能取得它的长度,还无法对它迭代。如果这对于你的应用而言是个问题,可以根据需要增加一些特殊方法(在Null 中直接增加或者从 Null 派生并扩展)并提供适当的实现,比如:

class SegNull(Null):def __len__(self):return 0 def __iter__(self):return iter(())def __getitem__(self,i):return selfdef __delitem__(self,i):return selfdef __setitem__(self,i,v):return self

利用此法也可增加其他操作。

Null 对象的关键设计目标是为我们在 Python 中常用的原始的 None 提供一种智能的替代物(其他语言中用null 或者 null 指针来代表无值或空值)。这种“这里什么也没有”的标记或占位符可以用于多种情况,比如其中一种情况是,一组元素除了其中一个比较特殊其他都是类似的。这种用法经常导致到处都是条件声明,而条件声明的目的常常只是为了将普通的元素和原始的null 值(比如 None)区分开来,Null 对象则能够帮助你避免过多的条件声明。
使用 Null 对象的优势如下:

  • 通过提供一个第一等的对象作为原始的 None 值的代替,过多的条件声明得以避免同时还提升了代码的可读性;
  • Null对象可以作为那些行为还没有完全实现的对象的占位符;
  • Null对象能够以一种多态的方式被任何其他类的实例使用(可能需要为某些特殊方法进行子类化扩展,正如前面提到过的那样);
  • Null对象具有很好的可预测性。

Null的一个很大的缺点是它可能会隐藏一些错误。如果一个函数返回 None,而调用者并不期望这样的返回值,调用者极有可能会接着对 None 调用一个它并不支持的方法或者操作,从而引发一个提示异常并回溯。如果返回值是调用者并不期望获得的Null,则问题可能会被掩盖很长一段时间,当最终异常和回溯发生时,重新定位到代码最初的纰漏会更加困难。这个问题严重到影响Null 的实用性了吗?答案是,这仍然是个人选择。如果你的代码在开发中总会有一些适当的单元测试,那么这个问题可能不会发生,而如果你根本就没有单元测试,使用 Null 带来的问题通常也只会是最小的问题。但正如我说的,这是个人的取舍。我喜欢到处用Null,我非常满意它给我带来的生产率的提升。

本节展示的 Null 类使用了“单例”模式(见6.15 节)的一个简单的变体,仅仅只是为了性能优化–说白了,就是为了避免创建很多什么也不做的对象,空耗内存。这个“单例”的实现确保了 Null 的每个子类在实例化的时候都只能有一个实例,这是很关键的。很显然,子类的数量不可能多到吃掉大量的内存,而各个子类之间的区分在语义上也非常重要。

相关文章:

  • 归并排序【逆序对】
  • Day04 新增套餐
  • 【堆】最大堆、最小堆以及GO语言的实现
  • 【Java Lambda表达式详解】
  • bellard.org‌ : QuickJS 如何使用 qjs 执行 js 脚本
  • 一种实波束前视扫描雷达目标二维定位方法——论文阅读
  • 搭建一个 gRPC 服务端和客户端
  • 青少年ctf练习平台--做题wp(2)
  • 使用python加edge-tts实现文字转语音
  • C++ 简单工厂模式详解
  • 游戏开发的TypeScript(3)匿名函数的常见和不常见用法
  • 基于 vue-flow 实现可视化流程图
  • Java学习手册:关系型数据库基础
  • C++ 中 virtual 的作用
  • 什么是函数重载?
  • 【开源免费】二维码批量识别-未来之窗——C#-仙盟创梦IDE
  • 在 Ubuntu 上安装 cPanel
  • 20:深度学习-多层感知器原理
  • Linxu基本操作
  • 计算机系统结构 第二章 :缓存优化
  • 巴基斯坦宣布禁止印度船只入港
  • 成为中国骑手孵化器,上海环球马术冠军赛是最好的历练舞台
  • 2025年五一档电影票房破4亿,《水饺皇后》领跑
  • 印尼巴厘岛多地停电,疑似海底电缆发生故障
  • 美国防部监察机构扩大“群聊门”事件调查范围
  • 三大猪企一季度同比均实现扭亏为盈,营收同比均实现增长