如何拦截对python的“魔术"的调用?新样式类中的方法? [英] How can I intercept calls to python's "magic" methods in new style classes?

查看:48
本文介绍了如何拦截对python的“魔术"的调用?新样式类中的方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在新样式类中拦截对 python 的双下划线魔术方法的调用.这是一个微不足道的例子,但它显示了意图:

class ShowMeList(object):def __init__(self, it):self._data = list(it)def __getattr__(self, name):attr = object.__getattribute__(self._data, name)如果可调用(属性):def 包装器(*a, **kw):打印通话前"结果 = attr(*a, **kw)打印通话后"返回结果返回包装器返回属性

如果我在列表周围使用该代理对象,我会得到非魔法方法的预期行为,但我的包装函数永远不会被魔法方法调用.

<预><代码>>>>l = ShowMeList(范围(8))>>>l #调用__repr__<__main__.ShowMeList 对象在 0x9640eac>>>>l.append(9)通话前通话后>>len(l._data)9

如果我不从对象继承(第一行 class ShowMeList:),一切都按预期工作:

<预><代码>>>>l = ShowMeList(范围(8))>>>l #调用__repr__通话前通话后[0, 1, 2, 3, 4, 5, 6, 7]>>>l.append(9)通话前通话后>>len(l._data)9

我如何用新的样式类完成这个拦截?

解决方案

出于性能原因,Python 总是在类(和父类)__dict__ 中查找魔术方法,并没有使用正常的属性查找机制.一种解决方法是使用元类在创建类时自动为魔术方法添加代理;例如,我使用这种技术来避免为包装类编写样板调用方法.

class Wrapper(object):"""提供对某些实例的代理访问的包装类内部实例."""__wraps__ = 无__ignore__ = "class mro new init setattr getattr getattribute"def __init__(self, obj):如果 self.__wraps__ 是 None:raise TypeError("基类 Wrapper 可能无法实例化")elif isinstance(obj, self.__wraps__):self._obj = obj别的:raise ValueError("包装的对象必须是 %s" % self.__wraps__)# 提供对包装对象常规属性的代理访问def __getattr__(self, name):返回 getattr(self._obj, name)# 为包装对象的双下划线属性创建代理类__元类__(类型):def __init__(cls, name, bases, dct):def make_proxy(名称):def代理(自我,*参数):返回 getattr(self._obj, name)返回代理type.__init__(cls, name, bases, dct)如果 cls.__wraps__:ignore = set("__%s__" % n for n in cls.__ignore__.split())对于目录中的名称(cls.__wraps__):如果 name.startswith("__"):如果名称不在忽略中且名称不在 dct 中:setattr(cls, name, property(make_proxy(name)))

用法:

class DictWrapper(Wrapper):__wraps__ = 字典Wrapped_dict = DictWrapper(dict(a=1, b=2, c=3))# 确保它有效....在wrapped_dict中断言b"#__contains__断言wrapped_dict == dict(a=1, b=2, c=3) # __eq__在 str(wrapped_dict) 中断言 "'a': 1" # __str__断言wrapped_dict.__doc__.startswith("dict()") # __doc__

I'm trying to intercept calls to python's double underscore magic methods in new style classes. This is a trivial example but it show's the intent:

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

If I use that proxy object around list I get the expected behavior for non-magic methods but my wrapper function is never called for magic methods.

>>> 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

If I don't inherit from object (first line class ShowMeList:) everything works as expected:

>>> 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

How do I accomplish this intercept with new style classes?

解决方案

For performance reasons, Python always looks in the class (and parent classes') __dict__ for magic methods and does not use the normal attribute lookup mechanism. A workaround is to use a metaclass to automatically add proxies for magic methods at the time of class creation; I've used this technique to avoid having to write boilerplate call-through methods for wrapper classes, for example.

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)))

Usage:

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__

这篇关于如何拦截对python的“魔术"的调用?新样式类中的方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆