【问题标题】:How can we force getattribute() to be called for magic methods (special methods)?我们如何强制 getattribute() 为魔术方法(特殊方法)调用?
【发布时间】:2018-08-04 14:12:16
【问题描述】:

python documentation 声明 __getattribute__ 在查找特殊方法时可能会被绕过。这是通过语言语法或内置函数隐式调用的结果。

例如,

elem = container[0]

不等于:

elem = container.__getattribute__('__getitem__')[0]

下面是另一个例子:

class WrappedList:
    def __init__(self):
        object.__setattr__(self, 'interal_list', ['apple', 'pear', 'orange'])

    def __getattribute__(self, attr_name):
        interal_list = object.__getattribute__(self, 'interal_list')
        attr = getattr(interal_list, attr_name)
        return attr

wl = WrappedList()

print("\nSTART TEST 01 ------------------------")
try:
    print(wl[0]) # throws TypeError: 'WrappedList' object does not support indexing
except TypeError as e:
    print(e)

print("\nSTART TEST 02 ------------------------")
try:
    getitem = getattr(wl, '__getitem__')
    print(getitem(0)) # works just fine
except TypeError as e:
    print(e)

我想写一个类似于MagicOverrider 的类,其中任何继承自MagicOverrider always 的类都会调用__getattribute__,而不是绕过它。我的问题是我们该怎么做?

我尝试了以下方法:

class MagicOverrider:

    def __call__(self, *args, **kwargs):
        f = getattr(self, '__call__')
        return f(*args, **kwargs)

    def __iter__(self, *args, **kwargs):
        f = getattr(self, '__iter__')
        return f(*args, **kwargs)

    def __getitem__(self, *args, **kwargs):
        f = getattr(self, '__getitem__')
        return f(*args, **kwargs)

    def __setitem__(self, *args, **kwargs):
        f = getattr(self, '__setitem__')
        return f(*args, **kwargs)

    def __add__(self, *args, **kwargs):
        f = getattr(self, '__add__')
        return f(*args, **kwargs)

    def __sub__(self, *args, **kwargs):
        f = getattr(self, '__sub__')
        return f(*args, **kwargs)

    def __mul__(self, *args, **kwargs):
        f = getattr(self, '__mul__')
        return f(*args, **kwargs)

    def __truediv__(self, *args, **kwargs):
        f = getattr(self, '__truediv__')
        return f(*args, **kwargs)

    def __floordiv__(self, *args, **kwargs):
        f = getattr(self, '__floordiv__')
        return f(*args, **kwargs)

    def __mod__(self, *args, **kwargs):
        f = getattr(self, '__mod__')
        return f(*args, **kwargs)

    def __divmod__(self, *args, **kwargs):
        f = getattr(self, '__divmod__')
        return f(*args, **kwargs)

    def __pow__(self, *args, **kwargs):
        f = getattr(self, '__pow__')
        return f(*args, **kwargs)

    def __lshift__(self, *args, **kwargs):
        f = getattr(self, '__lshift__')
        return f(*args, **kwargs)

    def __rshift__(self, *args, **kwargs):
        f = getattr(self, '__rshift__')
        return f(*args, **kwargs)

    def __and__(self, *args, **kwargs):
        f = getattr(self, '__and__')
        return f(*args, **kwargs)

    def __xor__(self, *args, **kwargs):
        f = getattr(self, '__xor__')
        return f(*args, **kwargs)

    def __or__(self, *args, **kwargs):
        f = getattr(self, '__or__')
        return f(*args, **kwargs)

    def __radd__(self, *args, **kwargs):
        f = getattr(self, '__radd__')
        return f(*args, **kwargs)

    def __rsub__(self, *args, **kwargs):
        f = getattr(self, '__rsub__')
        return f(*args, **kwargs)

    def __rmul__(self, *args, **kwargs):
        f = getattr(self, '__rmul__')
        return f(*args, **kwargs)

    def __rtruediv__(self, *args, **kwargs):
        f = getattr(self, '__rtruediv__')
        return f(*args, **kwargs)

    def __rfloordiv__(self, *args, **kwargs):
        f = getattr(self, '__rfloordiv__')
        return f(*args, **kwargs)

    def __rmod__(self, *args, **kwargs):
        f = getattr(self, '__rmod__')
        return f(*args, **kwargs)

    def __rdivmod__(self, *args, **kwargs):
        f = getattr(self, '__rdivmod__')
        return f(*args, **kwargs)

    def __rpow__(self, *args, **kwargs):
        f = getattr(self, '__rpow__')
        return f(*args, **kwargs)

    def __rlshift__(self, *args, **kwargs):
        f = getattr(self, '__rlshift__')
        return f(*args, **kwargs)

    def __rrshift__(self, *args, **kwargs):
        f = getattr(self, '__rrshift__')
        return f(*args, **kwargs)

    def __rand__(self, *args, **kwargs):
        f = getattr(self, '__rand__')
        return f(*args, **kwargs)

    def __rxor__(self, *args, **kwargs):
        f = getattr(self, '__rxor__')
        return f(*args, **kwargs)

    def __neg__(self, *args, **kwargs):
        f = getattr(self, '__neg__')
        return f(*args, **kwargs)

    def __pos__(self, *args, **kwargs):
        f = getattr(self, '__pos__')
        return f(*args, **kwargs)

    def __abs__(self, *args, **kwargs):
        f = getattr(self, '__abs__')
        return f(*args, **kwargs)

    def __invert__(self, *args, **kwargs):
        f = getattr(self, '__invert__')
        return f(*args, **kwargs)

    def __complex__(self, *args, **kwargs):
        f = getattr(self, '__complex__')
        return f(*args, **kwargs)

    def __int__(self, *args, **kwargs):
        f = getattr(self, '__int__')
        return f(*args, **kwargs)

    def __float__(self, *args, **kwargs):
        f = getattr(self, '__float__')
        return f(*args, **kwargs)

    def __round__(self, *args, **kwargs):
        f = getattr(self, '__round__')
        return f(*args, **kwargs)

    def __index__(self, *args, **kwargs):
        f = getattr(self, '__index__')
        return f(*args, **kwargs)

    def __eq__(self, *args, **kwargs):
        f = getattr(self, '__eq__')
        return f(*args, **kwargs)

    def __ne__(self, *args, **kwargs):
        f = getattr(self, '__ne__')
        return f(*args, **kwargs)

    def __lt__(self, *args, **kwargs):
        f = getattr(self, '__lt__')
        return f(*args, **kwargs)

    def __le__(self, *args, **kwargs):
        f = getattr(self, '__le__')
        return f(*args, **kwargs)

    def __gt__(self, *args, **kwargs):
        f = getattr(self, '__gt__')
        return f(*args, **kwargs)

    def __ge__(self, *args, **kwargs):
        f = getattr(self, '__ge__')
        return f(*args, **kwargs)

    def __bool__(self, *args, **kwargs):
        f = getattr(self, '__bool__')
        return f(*args, **kwargs)

    def __new__(self, *args, **kwargs):
        f = getattr(self, '__new__')
        return f(*args, **kwargs)

    def __del__(self, *args, **kwargs):
        f = getattr(self, '__del__')
        return f(*args, **kwargs)

    def __slots__(self, *args, **kwargs):
        f = getattr(self, '__slots__')
        return f(*args, **kwargs)

    def __hash__(self, *args, **kwargs):
        f = getattr(self, '__hash__')
        return f(*args, **kwargs)

    def __instancecheck__(self, *args, **kwargs):
        f = getattr(self, '__instancecheck__')
        return f(*args, **kwargs)

    def __subclasscheck__(self, *args, **kwargs):
        f = getattr(self, '__subclasscheck__')
        return f(*args, **kwargs)

    def __subclasshook__(self, *args, **kwargs):
        f = getattr(self, '__subclasshook__')
        return f(*args, **kwargs)

    def __ror__(self, *args, **kwargs):
        f = getattr(self, '__ror__')
        return f(*args, **kwargs)

    def __iadd__(self, *args, **kwargs):
        f = getattr(self, '__iadd__')
        return f(*args, **kwargs)

    def __isub__(self, *args, **kwargs):
        f = getattr(self, '__isub__')
        return f(*args, **kwargs)

    def __imul__(self, *args, **kwargs):
        f = getattr(self, '__imul__')
        return f(*args, **kwargs)

    def __itruediv__(self, *args, **kwargs):
        f = getattr(self, '__itruediv__')
        return f(*args, **kwargs)

    def __ifloordiv__(self, *args, **kwargs):
        f = getattr(self, '__ifloordiv__')
        return f(*args, **kwargs)

    def __imod__(self, *args, **kwargs):
        f = getattr(self, '__imod__')
        return f(*args, **kwargs)

    def __ipow__(self, *args, **kwargs):
        f = getattr(self, '__ipow__')
        return f(*args, **kwargs)

    def __ilshift__(self, *args, **kwargs):
        f = getattr(self, '__ilshift__')
        return f(*args, **kwargs)

    def __irshift__(self, *args, **kwargs):
        f = getattr(self, '__irshift__')
        return f(*args, **kwargs)

    def __iand__(self, *args, **kwargs):
        f = getattr(self, '__iand__')
        return f(*args, **kwargs)

    def __ixor__(self, *args, **kwargs):
        f = getattr(self, '__ixor__')
        return f(*args, **kwargs)

    def __repr__(self, *args, **kwargs):
        f = getattr(self, '__repr__')
        return f(*args, **kwargs)

    def __str__(self, *args, **kwargs):
        f = getattr(self, '__str__')
        return f(*args, **kwargs)

    def __cmp__(self, *args, **kwargs):
        f = getattr(self, '__cmp__')
        return f(*args, **kwargs)

    def __rcmp__(self, *args, **kwargs):
        f = getattr(self, '__rcmp__')
        return f(*args, **kwargs)

    def __nonzero__(self, *args, **kwargs):
        f = getattr(self, '__nonzero__')
        return f(*args, **kwargs)

    def __unicode__(self, *args, **kwargs):
        f = getattr(self, '__unicode__')
        return f(*args, **kwargs)

但是,我的解决方案至少有两个问题:

  • 如果在未来的 python 版本中引入新的魔法方法,它将不再起作用
  • 第一行,class MagicOverrider:,抛出TypeError: 'function' object is not iterable

【问题讨论】:

    标签: python python-3.x class metaclass new-style-class


    【解决方案1】:

    这很棘手。 因为当通过语言构造触发魔法方法时,Python 不会通过在正常情况下使用的正常属性检索路径(即使用__getattribute__ 等等):相反,每当一个特殊方法被分配给一个类,它被标记在类本身的二进制数据结构中(这是由 Python 解释器中的 C 代码完成的)。这样做是为了使这种用法是快捷方式 - 否则只是为了获得正确的方法来执行(例如添加或项目检索)将需要太多代码。还有,很容易有一些无限递归循环。

    所以 - 魔法方法总是直接在 Python 中检索 - 没有 __getattribute__

    可以做的是让魔术方法本身在运行时触发__getattribute__。如果他们得到任何与自己不同的结果,他们就会调用它。请注意避免无限递归。

    至于潜在的魔法方法:因为这无论如何都需要一个元类,所以只需让元类在创建将强制__getattribute__ 的类时包装所需类的所有魔法方法。

    下面的代码做到了这一点,并包含一个示例类,该类在__getitem__ 上放置了一个临时包装器:

    from functools import wraps
    from threading import local as thread_local
    from types import MethodType
    
    def wrap(name, method):
        local_flag = thread_local()
        @wraps(method)
        def wrapper(*args, **kw):
            local_method = method
            if not getattr(local_flag, "running", False) and args and not isinstance(args[0], type):
                local_flag.running = True
                # trigger __getattribute__:
                self = args[0]
                cls = self.__class__
                retrieved = cls.__getattribute__(self, name)
                if not retrieved is wrapper:
                    local_method =  retrieved
                if isinstance(local_method, MethodType):
                    args = args[1:]
            result = local_method(*args, **kw)
            local_flag.running = False
            return result
        wrapper._wrapped = True
        return wrapper
    
    
    class MetaOverrider(type):
        def __init__(cls, name, bases, namespace, **kwd):
            super().__init__(name, bases, namespace, **kwd)
    
            for name in dir(cls):
                if not (name.startswith("__")  and name.endswith("__")):
                    continue
                if name in ("__getattribute__", "__class__", "__init__"):
                    continue
                magic_method = getattr(cls, name)
                if not callable(magic_method) or getattr(magic_method, "_wrapped", False):
                    continue
                setattr(cls, name, wrap(name, magic_method))
    
    
    
    class TestOverriding(list, metaclass=MetaOverrider):
        def __getattribute__(self, attrname):
            attr = super().__getattribute__(attrname)
            if attrname == "__getitem__":
                original = attr
                def printergetitem(self, index):
                    print("Getting ", index)
                    return original(index)
                attr = printergetitem
            return attr
    

    它适用于任何魔术方法 - 当然,如果您只是在创建类后将魔术方法分配给类本身,它将影响使用的包装方法。但是对于 __getattribute__ 本身添加的任何魔术包装,它应该可以工作。

    【讨论】:

      【解决方案2】:

      您无法设置任何挂钩或选项来为所有魔术方法打开常规属性处理。您可以做到这一点的唯一方法是覆盖所有这些以单独委派给您想要的处理,这有一些重要的限制,您发现了其中一个:

      • 每次引入魔法方法都需要更新。
      • 很容易漏掉一个方法。
      • 无论您委托给什么属性查找过程,都可能会选择您的委托方法。这需要小心处理。

      您的尝试失败,因为您尝试将 __slots__ 定义为实例方法。 __slots__ 根本不应该是任何类型的方法,绝对不是实例方法;它需要是一个序列,并且需要在类定义时进行处理以确定类实例的布局。您还尝试将__new__ 编写为实例方法。但是,即使您没有犯这些错误,您的大多数方法也会最终陷入无限递归循环。

      【讨论】:

        猜你喜欢
        • 2015-08-08
        • 2010-11-08
        • 1970-01-01
        • 2016-04-09
        • 1970-01-01
        • 2014-11-29
        • 2018-01-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多