在元类中覆盖 __bases__

Posted

技术标签:

【中文标题】在元类中覆盖 __bases__【英文标题】:Overriding __bases__ in a metaclass 【发布时间】:2017-04-06 11:26:44 【问题描述】:

是否可以使用 get-set 属性覆盖元类的 __bases__ 字段(即派生自 type 的类)?以下代码适用于 getting C.__bases__,但不适用于 setting 它:

class Meta(type):
    @property
    def __bases__(cls):
        print('Getting __bases__ via Meta.__bases__')
        return super().__bases__

    @__bases__.setter
    def __bases__(cls, value):
        print('Setting __bases__ via Meta.__bases__')
        super().__bases__ = value

class A: pass
class B: pass
class C(A, B, metaclass=Meta): pass

# >>> C.__bases__
# Getting __bases__ via Meta.__bases__
# (<class '__main__.A'>, <class '__main__.B'>)
# >>> C.__bases__ = (B, A)
# Setting __bases__ via Meta.__bases__
# AttributeError: 'super' object has no attribute '__bases__'

我已经在 setter 函数中尝试了一些 super() 的替换,但它们都不起作用:

type.__setattr__(cls, '__bases__', value) 导致递归。

object.__setattr__(cls, '__bases__', value)TypeError: can't apply this __setattr__ to type object

所以,这归结为如何设置cls.__bases__ 字段 被元类上的属性所遮蔽。有什么想法吗?


(是的,我知道定义__bases__ 属性对类的实际__mro__ 没有影响,尽管可以通过覆盖mro() 来安排)

【问题讨论】:

不,你不能。为什么要这样做,这解决了什么问题? super()只支持非数据描述符(所以只支持__get__)。不支持__set__,因此super().__bases__ = value 将尝试在超级代理上设置__bases__ 属性,而不是在任何基类上。 __bases__ 无法更改。 Re: 我为什么要这样做:想法是实现一种“类代理”,通过复制其__mro__ 将方法和属性访问转发到代理类。如果类代理的__bases__ 也对应于代理类的__bases__,那就太好了,因此需要覆盖它。 设置__bases__ 非常罕见,不支持应该没问题。 【参考方案1】:

super() 不支持数据描述符,仅支持普通描述符,因为仅实现了 super().__get__

换个说法,作业

super().__bases__ = value

失败是因为super() 代理对象没有实现descriptor.__set__() method,因此该分配尝试将__bases__ 设置为该代理对象的属性。

您必须手动访问 type 对象上的描述符:

class Meta(type):
    @property
    def __bases__(cls):
        print('Getting __bases__ via Meta.__bases__')
        return type.__dict__['__bases__'].__get__(cls)

    @__bases__.setter
    def __bases__(cls, value):
        print('Setting __bases__ via Meta.__bases__')
        return type.__dict__['__bases__'].__set__(cls, value)

为了对称起见,我在 getter 中使用了相同的手动描述符访问,尽管 super().__bases__ 也可以工作。

以上,我硬编码type,而不是搜索MRO;您还可以使用辅助函数通过完整的 MRO 搜索找到正确的描述符:

class Meta(type):
    def _find_super(cls, name):
        mro = type(cls).__mro__
        idx = mro.index(__class__)
        for base in mro[idx + 1:]:
            if name in base.__dict__:
                return base.__dict__[name]
        return

    @property
    def __bases__(cls):
        print('Getting __bases__ via Meta.__bases__')
        return cls._find_super('__bases__').__get__(cls)

    @__bases__.setter
    def __bases__(cls, value):
        print('Setting __bases__ via Meta.__bases__')
        return cls._find_super('__bases__').__set__(cls, value)

无论哪种方式,现在您都可以拦截正在设置的__bases__

>>> class A: pass
...
>>> class B: pass
...
>>> class C(A, B, metaclass=Meta): pass
...
>>> C.__bases__
Getting __bases__ via Meta.__bases__
(<class '__main__.A'>, <class '__main__.B'>)
>>> C.__bases__ = (B, A)
Setting __bases__ via Meta.__bases__
>>> C.__bases__
Getting __bases__ via Meta.__bases__
(<class '__main__.B'>, <class '__main__.A'>)

【讨论】:

__bases__ 可写的。例如,在没有元类的情况下定义 C:&gt;&gt;&gt; class C(A, B): pass &gt;&gt;&gt; C.__bases__ = (B, A) 但是您关于super() 不支持数据描述符的观点解释了AttributeError。谢谢。 @SJPalt:我的立场是正确的!在那张纸条上,我已经包含了如何直接访问描述符。 这非常好,并且按预期工作。我没有想过像那样重新实现super()。谢谢你的回答。

以上是关于在元类中覆盖 __bases__的主要内容,如果未能解决你的问题,请参考以下文章

4.15---元类练习

元类对象

面向对象进阶6:元类

元类中 __new__ 的行为(也在继承的上下文中)

如何在 python 中使用元类来增加或覆盖添加到类中的方法

3.1.18 元类的练习