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

Python Cookbook-6.5 继承的替代方案——自动托管

任务

你需要从某个类或者类型继承,但是需要对继承做一些调整。比如,需要选择性地隐藏某些基类的方法,而继承并不能做到这一点。

解决方案

继承是很方便的,但它并不是万用良药。比如,它无法让你隐藏基类的方法或者属性。而自动托管技术则提供了一种很好的选择。假设需要把一些对象封起来变成只读对象从而避免意外修改的情况。那么,除了禁止属性设置的功能,还需要隐藏修改属性的方法。下面我们给出一个办法:

#同时支持2.3和2.4
try:set
except NameError:from sets import Set as set
class ROError(AttributeError):pass
class Readonly:#这里并没有用继承,我们会在后面讨论其原因mutators = {list:set('''__delitem__ __delslice__ __iadd__ __imul____setitem__ __setslice__ __append extend insert pop remove sort'''.split()),dict:set('''__delitem__ __setitem__ clear pop popitemsetdefault update'''.split()),}def __init__(self,o):object.__setattr__(self,'_o',o)object.__setattr__(self,'_no',self.mutators.get(type(o),()))def __setattr__(self,n,y):raise ROError,"Can't set attr %r on RO obiect" %n def __delattr__(self,n):raise ROError,"Can't del attr %r from Ro object" %n def __getattr__(self,n):if n in self._no:raise ROError,"Can't get attr %r from Ro object" %nreturn getattr(self._o, n)

通过修改 mutators,即 Readonly.mutators[sometype] = the_mutators,还可以轻松地增加其他需要处理的类型。

讨论

自动托管是一种强大而通用的技术。在本节的例子中,通过使用这个技术我们能得到和类继承几乎完全一样的效果,同时还能隐藏一些名字。我们在任务中使用这个模拟的子类将一些对象封装起来,使之变成只读对象。它的性能也许不如真正的继承,但另一方面,作为补偿,我们获得了更好的灵活度和更精细的粒度控制。

基本的想法是,我们的类的每个实例都含有我们想要封装的类型的实例。每当客户代码试图从我们的类的实例中获取属性时,除非该属性已经在类中被定义了(比如定义在 Readonly类的 mutators 字典中),否则__getattr__ 在完成检查之后,会透明地将这个请求转交给被封装的实例。在Python中,方法同样也是属性,访问的方式也一样,所以无论是访问方法还是属性,代码无须改变。用来访问属性的__getattr__方法同时也可用于访问方法。

解决方案的注释没有解释不使用继承的原因,这里我们会给出一点解释。这种基于__getattr__的方式也可用于特殊方法,但仅对旧风格类的实例有效。在新的对象模型中,Python 操作直接通过类的特殊方法来进行,而不是实例的。关于这个问题的更多内容可以在 6.6 节和 20.8节中看到。本节采用的方案——让 Readonly 类成为旧风格类,从而避开这个问题,并把相关内容留到其他章节——在真实的生产代码中是不值得推荐的。我在这里用仅仅是为了控制篇幅,同时避免重复其他章节的内容。

setattr__的角色类似于__getattr,当客户代码设置实例的属性时,它就会被调用,这个任务要求某些属性为只读,我们只需简单地禁止属性访问操作即可。记住,要在方法的代码编写中避免激发对__setattr__的调用,在有__setattr__的类的方法中你不应该使用self.n = v这样的语句。最简单的是直接把设置操作委托给类object,如同类Readonly在它的__init__ 方法中所做的那样。方法__delattr__完成了最后拼图,它会处理那些试图从实例中删除属性的操作。

以自动托管方式完成的封装并不适用于采用了类型检查的客户代码或者框架代码。在那种情况下,客户代码或框架代码完全破坏了多态性,代码本身应该是被重写的。记住不要在你自己的代码中使用类型检查,因为你可能根本无须那么做。见6.13节提供的更好的选择。

在 Python 的老版本中,自动托管的流行程度甚至比现在还高,那是因为当时 Python 不支持从内建的类型继承。而对于现在的 Python,从内建类型继承是允许的,因此自动托管就用得不那么频繁了。不过,自动托管仍然具有它的地位——它只是稍微远离了聚光灯一点点。托管比继承更加灵活,而有时这种灵活是无价的。除了选择性地托管(从而高效地实现了某些属性的“隐藏”),一个对象还可以在不同的时间托管给不同的子对象,或者一次托管给多个子对象,继承无法提供任何能够与之相比的特性。下面给出托管给多个特定子对象的例子。假设你有个类,提供各种“转发方法”,比如:

class Pricing(object):def __init__(self,location,event):self.location = locationself.event = eventdef setlocation(self,location):self.location = locationdef getprice(self):return self.location.getprice()def getquantity(self):return self.location.getquantity()def getdiscount(self):return self.event.getdiscount()and many more such methods

继承很明显不适用,因为 Pricing的实例需要托管给特定的location和event实例,这些实例在初始化阶段传入而且可能会被修改。自动托管的补救方法是:

class AutoDelegator(object):delegates = ()do_not_delegate = ()def __getattr__(self,key):if key not in self.do_not_delegate:for d in self.delegates:try:return getattr(d,key)except AttributeError:passraise AttributeError,key
class Pricing(AutoDelegator):def __init__(self,location,event):self.delegates = [location,event]def setlocation(self,location):self.delegates[0] = location

在此例中,我们没有托管属性的删除和设置,而只是托管了属性的获取(还有一些非特殊方法)。当然,这个方式只有在我们想要托管的各个对象的方法(以及其他属性)不会互相干扰的情况下才会充分有效,比如,location最好不要有个getdiscount方法否则它会抢先进行方法的托管,而此方法原本应该是由event对象来执行的。

如果一个需要大量托管的类涉及这种问题,它可以简单地定义一些对应的方法,这是因为只有在用别的方式无法找到属性和方法时,__getattr__才会介入。而通过do_not_delegate 属性还可以隐藏托管对象的一些属性和方法,而且它也可以被子类改写。举个例子,如果类 Pricing 想要隐藏一个叫做 setdiscount 的方法,此方法由 event提供,做一点点修改就可以了:

class Pricing(AutoDelegator):
do_not_delegate = ('set_discount')

其余部分则与前面代码片段相同。

相关文章:

  • Vue 3 路由配置使用与讲解
  • 脚本-QQ批量发送消息(图片和文字)
  • 23黑马产品经理Day01
  • 用户态与内核态多个维度的区别
  • 爬虫(基本知识介绍,urllib库的说明)
  • 波束形成(BF)从算法仿真到工程源码实现-第十二节-总结
  • Redis原理与Windows环境部署实战指南:助力测试工程师优化Celery调试
  • Java Bean演进历程:从POJO到Spring Boot配置绑定
  • Vue3 SSR生物启发架构:仿生渲染与DNA流式编码
  • 解决JSON格式数据大小写问题,以及@JsonProperty 和@JSONField序列化的区别
  • TVS管与ESD保护二极管详解:原理、区别与应用选型
  • 广东广州一家IPO资产重组疑点重重,信息披露真实性存疑
  • 通过高斯分布概率密度函数寻找到数据中的异常点
  • TLS/SSL 弱密码套件中危漏洞修复
  • Security 权限控制的基本流程
  • 基于 Python 的 ROS2 应用开发全解析
  • 【扩散模型(十三)】Break-A-Scene 可控生成,原理与代码详解(中)Cross Attn Loss 代码篇
  • tcp和udp的数据传输过程以及区别
  • neakyThrows 是 Lombok 库中的一个注解
  • LeetCode Hot100 刷题笔记(10)—— ACM格式输入输出练习
  • 网站建设与推广的步骤/关键词优化排名详细步骤
  • 外贸狼/北京朝阳区优化
  • 做网站襄樊/百度网盘怎么找资源
  • 政府网站内容建设/微信拓客的最新方法
  • 云南 网站建设/惠州seo排名
  • 龙岩网站建设设计制作/怎么建网站教程图解