为何property装饰器会重写object.__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函数,核心逻辑是:
- 优先识别
property自身的专属属性(比如fget、setter、getter等),直接返回对应的值; - 如果是其他属性,再调用默认的
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

