You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何property装饰器会重写object.__getattribute__方法?

property装饰器重写__getattribute__的作用探究

我注意到,和classmethod、staticmethod装饰器不同,property装饰器重写了object.__getattribute__方法:

>>> list(vars(classmethod))
['__new__', '__repr__', '__get__', '__init__', '__func__', '__wrapped__', '__isabstractmethod__', '__dict__', '__doc__']
>>> list(vars(staticmethod))
['__new__', '__repr__', '__call__', '__get__', '__init__', '__func__', '__wrapped__', '__isabstractmethod__', '__dict__', '__doc__']
>>> list(vars(property))
['__new__', '__getattribute__', '__get__', '__set__', '__delete__', '__init__', 'getter', 'setter', 'deleter', '__set_name__', 'fget', 'fset', 'fdel', '__doc__', '__isabstractmethod__']

根据《Descriptor指南》里的等效Python代码,property的核心功能似乎并不需要这一重写。那这个重写到底实现了什么特殊行为?


重写__getattribute__的核心作用

property重写__getattribute__是为了避免在访问property自身属性时触发描述符协议。

当你直接访问一个property实例的属性(比如prop.fget、prop.setter)时,如果没有重写__getattribute__,Python会调用property的__get__方法(因为它是描述符),而不是直接返回实例的属性。重写后,访问property实例自身的属性会直接返回对应的值,不会触发描述符的__get__逻辑。

举个实际的例子:如果没有这个重写,当你尝试获取prop.fget时,Python会把prop当作描述符,调用prop.__get__(None, property),最终返回prop本身——这显然不是我们想要的,我们只是想拿到property实例自带的fget属性,而不是触发它的取值逻辑。

CPython中的实现逻辑

在CPython源码中,property的__getattribute__对应Objects/descrobject.c文件里的property_getattro函数,核心逻辑是:

  1. 优先识别property自身的专属属性(比如fget、setter、getter等),直接返回对应的值;
  2. 如果是其他属性,再调用默认的object.__getattribute__处理。

等效Python代码

下面是模拟这一行为的等效Python代码:

class Property:
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(instance)

    def __set__(self, instance, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(instance, value)

    def __delete__(self, instance):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(instance)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

    # 重写__getattribute__,避免访问自身属性时触发__get__
    def __getattribute__(self, name):
        # 优先处理property自身的特殊属性
        if name in ('fget', 'fset', 'fdel', '__doc__', 'getter', 'setter', 'deleter'):
            return object.__getattribute__(self, name)
        # 其他属性交给默认逻辑处理
        return object.__getattribute__(self, name)

测试验证:

class MyClass:
    @Property
    def x(self):
        return 1

p = MyClass.x
print(p.fget)  # 直接返回getter函数,不会触发__get__

如果去掉__getattribute__重写,运行print(p.fget)会调用p.__get__(None, MyClass),最终返回p本身,完全不符合我们获取fget属性的预期。


内容的提问来源于stack exchange,提问作者Géry Ogam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 06:43:31