任务
在新风格对象模型中,Python 操作其实是在类中查找特殊方法的(而不是在实例中那是经典对象模型的处理方式)。现在,需要将一些新风格的实例包装到代理类中,此代理可以选择将一些特殊方法委托给内部的被包装对象。
解决方案
你需要即时地生成各个代理类。如下,
class Proxy(object):
"""所有代理的基类"""
def __init__(self,obj):
super(Proxy,self).__init__(obj)
self.obj = obj
def __getattr__(self,attrib):
return getattr(self._obj,attrib)
def make_binder(unbound_method):
def f(self,*a,**k): return unbound_method(self._obj, *a,**k)
#仅2.4:f.__name__ = unbound_method.__name__
return f
known_proxy_classes = { }
def proxy(obj,*specials):
'''能够委托特殊方法的代理的工厂函数'''
#我们手上有合适的自定义的类吗?
obj_cls = obj.__class__
key = obj_cls, specials
cls = known_proxy_classes.get(key)
if cls is None:
#我们手上没有合适的类,那就现做一个
cls = type("%sProxy" %obj_cls.__name__, (Proxy,){})
for name in specials:
name = '__%s__' %name
unbound_method = getattr(obj_cls,name)
setattr(cls,name,make_binder(unbound_method))
#缓存之以供进一步使用
known_proxy_classes[key] = cls
#实例化并返回需要的代理
return cls(obj)
讨论
代理和自动托管都是 Python 中的玩具,这得归功于__getattr__的机制。在查询任何属性时(包括方法,Python并不区分两者),Python都会自动调用__getattr__。
在旧风格(经典)对象模型中,__getattr__同样适用于特殊方法,这些方法常常被认为是 Python 操作的一部分。在使用中也需要当心错误地提供一个我们不想提供的特殊方法。而现在,在新代码中使用新风格的对象模型成为推荐方式:速度更快,更符合规定,特性也更丰富。当你从 object或任何内建类型派生子类时就得到了新风格的类。也许几年后的某一天,Python3.0将彻底地剔除经典对象模型以及其他一些仅仅是为了保证向后兼容的特性。(参看 http://www.python.org/peps/pep-3000.html 提供的关于Python 3.0 的计划细节,几乎都是以简化语言为主,而不是新增功能。)
在新风格对象模型中,Python 操作并不会在运行时查找特殊方法:它们依赖于类对象的“槽”(slots)。这些槽会在类对象被创建或者修改时更新。因此,对于一个代理对象,如果它要把特殊方法托管给被封装的对象,它本身必须属于某个量身定做的类。幸好如同解决方案的代码所示,在 Python中,凭空创造并实例化类是一件很简单的事情。
在本节,我们不使用任何高级的 Python 概念,比如自定义元类和描述符。事实上,每个代理都是由一个工厂函数 proxy 创建的,该函数接受一个封装的对象以及要托管的特殊方法的名字作为参数(除去前面和后面的两个下划线符号)。如果你把解决方案中的代码存为一个文件 proxy.py并放入你的 Python的 sys.path 中,就可以用下面的方法在 Python 解释器中使用它:
>>> import proxy
>>>a = proxy.proxy([],'len','iter')#只托管len和iter
>>> a#__repr__未被托管
<proxy.listProxy object at 0x0113C370>
>>> a.__class__
<elass 'proxy.listProxy'>
>>>a._obj
[ ]
>>>a.append#所有的非特殊方法都被托管了
<built-in method append of list object at 0x010F1A10>
由于__len__被托管了,于是len(a)像预期那样工作:
>>> len(a)
0
>>> a.append(23)
>>>len(a)
1
由于__iter__被托管了,for 循环也如同预期那样工作,和通过内建的list、sum、max等操作执行的循环一样:
>>>for x in a:print x
...
23
>>> list(a)
[23]
>>>sum(a)
23
>>> max(a)
23
不过,由于__getitem__没有被托管,a无法进行索引或切片操作:
>>> a.__getitem__
<method-wrapper object at0x010F1AF0>
>>> a[1]
Traceback (most recent call last):
File "<interactive input>",line l, in ?
TypeError:unindexable obiect
函数 proxy 使用的是以前创建的类的“缓存”,即全局字典 known_proxy_classes,它以被封装的对象的类和被托管的特殊方法的名字为键。如果要生成一个新类,proxy 就调用内建的 type,将新类的名字作为一个参数传入(在被包装的对象的类名后加了个“Proxy”),类 Proxy 作为唯一的基类,是一个“空”的类字典(它还没有加入任何属性)。基类 Proxy 进行初始化处理和对普通属性的查询的托管。然后,工厂函数 proxy 循环处理被托管的特殊方法名:对每一个名字,它都从被封装对象的类获得未绑定方法,并将其作为一个属性赋给闭包make binder中的新类。当遇到对这些未绑定方法的调用时,make_binder会提供一个适合的参数(比如被封装对象本身,self.obj)。
一旦完成了新类的准备,proxy将其存入known_proxy_classes,并以适当的键标记之最后,无论类是被创建或者从known_proxy_classes中获取的,proxy 都会使用被封装对象作为唯一参数,将其实例化,然后返回最后的代理实例。