如何在新样式类中拦截对python的“魔术”方法的调用?

发布于 2021-01-29 17:11:54

我正在尝试在新样式类中拦截对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

如何使用新样式类完成此拦截?

关注者
0
被浏览
53
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    出于性能原因,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__
    


知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看