覆盖元类中的__bases__

时间:2016-11-22 17:11:19

标签: python python-3.x inheritance metaclass

是否可以使用get-set属性覆盖元类的__bases__字段(即从type派生的类)?以下代码适用于获取 C.__bases__,但不适用设置

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()来安排<) / SUP>

1 个答案:

答案 0 :(得分:2)

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