在Python中完全封装对象
我想要完全包裹一个对象,这样对它的所有属性和方法的请求都能转发到它所包裹的对象上。同时,我还想重写一些我想要的方法或变量,并提供一些我自己的方法。这个包装类应该看起来和原来的类一模一样(也就是说,使用isinstance
时,它应该表现得就像真的属于那个类),但是单纯的继承是不够的,因为我想要包裹的是一个已经存在的对象。在Python中有没有什么解决方案可以做到这一点呢?我在想类似这样的方式:
class ObjectWrapper(BaseClass):
def __init__(self, baseObject):
self.baseObject = baseObject
def overriddenMethod(self):
...
def myOwnMethod1(self):
...
...
def __getattr__(self, attr):
if attr in ['overriddenMethod', 'myOwnMethod1', 'myOwnMethod2', ...]
# return the requested method
else:
return getattr(self.baseObject, attr)
不过我对重写__getattr__
、__setattr__
和__hasattr__
这些东西不太熟悉,所以不太确定该怎么做才对。
6 个回答
__getattr__
的好处在于,只有在你请求的属性不存在时,它才会被调用。所以你不需要一个明确的属性列表——任何你没有定义的属性都会自动处理。
__setattr__
就比较复杂,因为它总是会被调用。当你设置自己的属性时,一定要使用父类的调用或者 object.__setattr__
;如果在 __setattr__
中使用 setattr()
,会导致无限循环。
最后一点,涉及到 isinstance 的部分非常棘手。你可以通过给你的包装实例的 .__class__
变量赋值来实现(但这也会覆盖类字典的解析顺序),或者通过使用元类动态构建你的包装类型。由于在 Python 代码中 isinstance 使用得很少,实际上去尝试欺骗它似乎有些过于复杂。
请查看这个链接 http://code.activestate.com/recipes/577555-object-wrapper-class/,里面有完整的代码和重要的注释。简单来说就是:
class Wrapper(object):
def __init__(self, obj):
self._wrapped_obj = obj
def __getattr__(self, attr):
if attr in self.__dict__:
return getattr(self, attr)
return getattr(self._wrapped_obj, attr)
在大多数情况下,最简单的方法可能是:
class ObjectWrapper(BaseClass):
def __init__(self, baseObject):
self.__class__ = type(baseObject.__class__.__name__,
(self.__class__, baseObject.__class__),
{})
self.__dict__ = baseObject.__dict__
def overriddenMethod(self):
...
通过这种方式工作,也就是说重新指定自己的 __class__
和 __dict__
,你只需要提供你想要覆盖的部分——Python正常的属性获取和设置机制会处理其他的事情…… 大部分情况下。
只有当 baseObject.__class__
定义了 __slots__
时,你才会遇到麻烦。在这种情况下,多重继承的方法就不管用了,你确实需要那些麻烦的 __getattr__
(就像其他人说的,至少你不需要担心它会被调用来处理你正在覆盖的属性,因为它不会!),还有 __setattr__
(这就麻烦了,因为它会对每个属性都被调用),等等;而且让 isinstance
和特殊方法正常工作需要非常细致和繁琐的工作。
简单来说,__slots__
意味着一个类是特别的,每个实例都是一个轻量级的“值对象”,不应该进行进一步复杂的操作、封装等,因为为了每个该类的实例节省几字节的内存,牺牲了所有正常的灵活性考虑;因此,处理这种极端、罕见的类,跟处理99%以上的Python对象一样顺畅灵活,确实是一种痛苦。那么,你是否需要处理 __slots__
(甚至需要写、测试、调试和维护数百行代码来应对这些边缘情况),还是说用六行代码就能解决99%的问题就足够了?-)
还需要注意的是,这可能会导致内存泄漏,因为创建一个子类会将这个子类添加到基类的子类列表中,而当所有子类的实例被垃圾回收时,它并不会被移除。