__getattribute__ 实例和 class

__getattribute__ on instance and class

我有一个 metaclass 和 class,它们都使用 __getattribute__ 来拦截属性调用。它们看起来像这样:

class B(type):    
    def __getattribute__ (self, name) :
        print(f'hello {self}')
        return super().__getattribute__(name)

class C(metaclass=B):
    MY_ATTR = 'hello attr'

    def __getattribute__ (self, name) :
        print(f'hello {self}')
        return super().__getattribute__(name)

这符合我的预期:

C.MY_ATTR  
# hello <class 'C'>
# 'hello attr'

C().MY_ATTR
# hello <C object at 0x10bcbbda0>
# 'hello attr'

现在我想从 BC 中取出重复的代码并让它被继承。幸运的是,我称它们为 BC,并为 A 留了空间。我们开始吧:

class A:
    def __getattribute__ (self, name) :
        print(f'hello {self}')
        return super().__getattribute__(name)

class B(type, A):
    pass

class C(A, metaclass=B):
    MY_ATTR = 'hello attr'

不幸的是,这不再像以前那样:

C.MY_ATTR  
# 'hello attr'

C().MY_ATTR
# hello <C object at 0x10bcbbda0>
# 'hello attr'

我认为问题出在无法从常规 class 继承的 MetaClass 周围,但我不确定。我也对获得相同行为的任何其他实现(可能不需要 metaclass)持开放态度——尽管我仍然希望像 C.MISSING 这样的调用引发 AttributeError.

也有类似的问题(例如 Get attributes for class and instance in python),但它们略有不同,并没有达到我想要的效果。

谢谢

比那复杂一点- Metaclasses do 接受普通的 classes 作为 mixin - 并且这些 mixin 上提供的任何方法都将在使用这些元的 classes 上可用class 就好像它们是 class 方法一样(但它们将从实例中隐藏 - 因为 n 实例上的属性查找查找 class 上的属性,而不是元 class).

然而 __getattribute__ 是一个野兽 - 即使不涉及元 class 也很难正确地做到这一点(不像 __getattr__)。

我自己做了一些试验和错误,发现 type__getattribute__ 确实存在并且不同于 object 的 - 这就是为什么你自定义版本在执行 class M(type, A) 继承时不会调用。而且,如果你交换继承顺序,你会看到 super() 得到一个极端情况,它没有填写正确调用 type.__getattribute__ 所需的参数。

我试图用 if 语句硬编码对 type.__getattribute__ 的调用,并进入了一个属性查找循环,我无法弄清楚 - 这件事很重要(但是可能是由于 IPython 自省 - 见下文)。

尽管如此,在 class 和 metaclass 上重用代码本身是我从未见过的事情。

如果你想在 __getattribute__ 主体中做的事情很重要(与示例的 print 相对),我建议你将 __getattribute__ 主体分解为一个普通函数,并在示例中放置 print 的位置显式调用它 - 并且只在 metaclass 和 class base:[=30= 上保留三行 __getattribute__ ]

def getattribute(instance_or_class, attr_name):
    # complex attr_name consuming body goes here.
    # even colateral effects on "self" can be applied
    # to the  "instance_or_class" object
    print(instance_or_class, attr_name)


class B(type):
    def __getattribute__ (cls, name):
        getattribute(cls, name)
        return super().__getattribute__(name)


class C(metaclass=B):
    MY_ATTR = 'hello attr'
    def __getattribute__ (self, name):
        getattribute(self, name)
        return super().__getattribute__(name)

现在,我看到与您的第一个输出不同的一件事 - 我实际上得到:

In [43]: C().MY_ATTR
<class '__main__.C'> __class__
<class '__main__.C'> __class__
<__main__.C object at 0x7fa420a314a8> MY_ATTR
Out[43]: 'hello attr'

即Python先查找C__class__属性-

等等,仔细检查,在常规 Python 提示符下执行此操作时,不会发生这些调用 - 然后是 Ipython 触发,同时它是内省魔法。我陷入的递归循环可能是由于 IPython 的自省 - 尽管如此,你将避免很多不需要的魔法让 __getattribute__ 在 metaclass 和 base classes,并按上述方式分解。

>>> C().MY_ATTR
<__main__.C object at 0x7f0d4d908898> MY_ATTR
'hello attr'
>>> C.MY_ATTR
<class '__main__.C'> MY_ATTR
'hello attr'
>>>