Python:元类属性有时会覆盖类属性?

8 投票
2 回答
970 浏览
提问于 2025-04-17 23:29

下面这段代码的结果让我很困惑:

class MyClass(type):
    @property
    def a(self):
        return 1

class MyObject(object):
    __metaclass__ = MyClass

    a = 2

print MyObject.a
print object.__getattribute__(MyObject, 'a')
print type.__getattribute__(MyObject, 'a')
print MyObject.__dict__['a']
print MyObject().a

我本以为它只会重复打印 2,但实际上它打印的是 1 1 1 2 2。有没有什么方法可以让这个结果更容易理解呢?


为了更清楚一点:我知道这种行为有很多文档说明(在这里,"数据描述符"),但我想理解为什么会这样,以及为什么核心开发者会以这种方式实现描述符。

2 个回答

-1

这里有一些非常相似的代码,可能会帮助你理解发生了什么。

class MyClass(object):
    def __init__(self, data):
        self.__dict__.update(data) # this would fail if I had self.a = a

    @property
    def a(self):
        return 1

MyObject = MyClass({'a': 2})

print MyObject.a
print object.__getattribute__(MyObject, 'a')
print MyObject.__dict__['a']

你看到的是,实例(类的一个对象)在它的类上有一个描述符(你的属性),同时还有一个同名的属性。通常情况下,会有一些保护措施来防止这种情况,但由于type的工作方式,这些保护措施被绕过了。

所以你有

 print MyObject.a

描述符优先于__dict__中的条目,因此会调用property。这是因为object.__getattribute__的实现,至少从概念上来说是这样的。

 print object.__getattribute__(MyObject, 'a')

这和说MyObject.a是一样的,除非object.__getattribute__被重写了。__getattribute__就是最开始尝试描述符的行为来源。

 print type.__getattribute__(MyObject, 'a')

这和object.__getattribute__是一样的,因为type并没有重写__getattribute__

 print MyObject.__dict__['a']

这会在你的__dict__中查找,这是你存储2的唯一地方。这只是一个字典对象(可能/几乎是),所以它不会去其他地方查找。

 print MyObject().a

属性的工作方式是,你并不是以直接访问的方式来访问你类型的属性。这可能是没有特别直观答案的部分。

3

属性是一些特殊的数据描述符;在查找属性时,它们会优先于类实例字典中同名的条目。这意味着在

MyObject.a

中,MyClass里的a属性会优先于MyObject字典里的a条目。同样,

object.__getattribute__(MyObject, 'a')
type.__getattribute__(MyObject, 'a')

object.__getattribute__type.__getattribute__这两个方法也会优先考虑数据描述符,而不是实例字典中的条目,所以属性会胜出。

另一方面,

MyObject.__dict__['a']

这个操作是明确地查找字典。它只会查看MyObject的字典,忽略正常的属性查找机制。

对于最后一行:

MyObject().a

MyClass的描述符只适用于MyClass的实例,而不适用于它的实例的实例。属性查找机制不会看到这个属性。

撰写回答