如何在新样式类中拦截对python的“魔术”方法的调用?
我正在尝试在新样式类中拦截对python的双下划线魔术方法的调用。这是一个简单的示例,但它显示了其意图:
class ShowMeList(object):
def __init__(self, it):
self._data = list(it)
def __getattr__(self, name):
attr = object.__getattribute__(self._data, name)
if callable(attr):
def wrapper(*a, **kw):
print "before the call"
result = attr(*a, **kw)
print "after the call"
return result
return wrapper
return attr
如果我在列表周围使用该代理对象,则可以得到非魔术方法的预期行为,但是我的包装函数从未被魔术方法调用。
>>> l = ShowMeList(range(8))
>>> l #call to __repr__
<__main__.ShowMeList object at 0x9640eac>
>>> l.append(9)
before the call
after the call
>> len(l._data)
9
如果我没有从object继承(第一行class ShowMeList:
),那么一切都会按预期进行:
>>> l = ShowMeList(range(8))
>>> l #call to __repr__
before the call
after the call
[0, 1, 2, 3, 4, 5, 6, 7]
>>> l.append(9)
before the call
after the call
>> len(l._data)
9
如何使用新样式类完成此拦截?
-
出于性能原因,Python始终在类(和父类的类)中
__dict__
查找魔术方法,并且不使用常规的属性查找机制。一种解决方法是在类创建时使用元类为魔术方法自动添加代理。例如,我使用这种技术来避免必须为包装器类编写样板调用方法。class Wrapper(object): """Wrapper class that provides proxy access to an instance of some internal instance.""" __wraps__ = None __ignore__ = "class mro new init setattr getattr getattribute" def __init__(self, obj): if self.__wraps__ is None: raise TypeError("base class Wrapper may not be instantiated") elif isinstance(obj, self.__wraps__): self._obj = obj else: raise ValueError("wrapped object must be of %s" % self.__wraps__) # provide proxy access to regular attributes of wrapped object def __getattr__(self, name): return getattr(self._obj, name) # create proxies for wrapped object's double-underscore attributes class __metaclass__(type): def __init__(cls, name, bases, dct): def make_proxy(name): def proxy(self, *args): return getattr(self._obj, name) return proxy type.__init__(cls, name, bases, dct) if cls.__wraps__: ignore = set("__%s__" % n for n in cls.__ignore__.split()) for name in dir(cls.__wraps__): if name.startswith("__"): if name not in ignore and name not in dct: setattr(cls, name, property(make_proxy(name)))
用法:
class DictWrapper(Wrapper): __wraps__ = dict wrapped_dict = DictWrapper(dict(a=1, b=2, c=3)) # make sure it worked.... assert "b" in wrapped_dict # __contains__ assert wrapped_dict == dict(a=1, b=2, c=3) # __eq__ assert "'a': 1" in str(wrapped_dict) # __str__ assert wrapped_dict.__doc__.startswith("dict()") # __doc__