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

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 的每个子类在实例化的时候都只能有一个实例,这是很关键的。很显然,子类的数量不可能多到吃掉大量的内存,而各个子类之间的区分在语义上也非常重要。

http://www.xdnf.cn/news/3685.html

相关文章:

  • PyTorch_张量元素类型转换
  • MySQL索引和事务
  • 接口测试的核心思维(基础篇)
  • Java 中如何实现自定义类加载器,应用场景是什么?
  • 如何快速有效学习数字社会学AI社会学,抓住网络社会学知识图谱,数字社会学50个核心概念
  • Hal库下备份寄存器
  • 字母异位词分组(中等)
  • 继承【Java版】详细讲解
  • 虚幻引擎入门笔记
  • 山东大学计算机组成与设计第七章习题解析
  • Nginx — 防盗链配置
  • 深度学习核心架构:探明四种基础神经网络
  • 从基础到实践(三十六):RTC时钟芯片的应用
  • 多线程系列三:这就是线程的状态?
  • 什么是生成式 AI (GenAI)?
  • 强化学习--2.数学
  • 摩尔缠论课程合集完整版核心课程前置课程圈子问答星球圈子摩尔缠论三个阶段
  • redis延时队列详细介绍
  • Dart和Go语言特征对比
  • 接上篇,解决FramePack启动报错:“httpx.ReadError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。“的问题
  • 关于项目中优化使用ConcurrentHashMap来存储锁对象
  • 【C语言练习】019. 使用结构体数组存储复杂数据
  • 【unity游戏开发入门到精通——UGUI】整体控制一个UGUI面板的淡入淡出——CanvasGroup画布组组件的使用
  • 基于D-Mixer与TransXNet的YOLOv8改进—融合全局-局部特征与空间降维注意力机制的CNN-ViT混合架构
  • 三、shell脚本--运算符与表达式:让脚本学会“思考”
  • 高中数学联赛模拟试题精选学数学系列第4套几何题
  • 数据的存储
  • Python表达式全解析:从基础到高级
  • 开源项目实战学习之YOLO11:ultralytics-cfg-models-nas(十)
  • C++的内存