第8.27节 Python中__getattribute__与property的fget、@property装饰器getter关系深入解析

本文通过案例分析了Python中使用@property装饰器和property函数定义属性的方法,对比了这两种方式与__getattribute__方法的区别,帮助理解属性访问机制。

一、 引言
《第7.23节 Python使用property函数定义属性简化属性访问的代码实现》《第7.26节 Python中的@property装饰器定义属性访问方法getter、setter、deleter 详解》中介绍了两种设置属性访问方法,通过设置可以在相关属性访问时调用对应的方法执行访问,支持属性简单访问(如对象名.属性名、赋值语句)、或者为了控制访问逻辑使用的。那么property函数其中的fget、@property装饰器的getter之间的关系是怎样的呢?下面我们通过案例来进行分析。

二、 案例

  1. 案例说明:
    本案例定义类Car,其中有二个实例变量power、totaldistance、三个实例方法(构造方法、__ getattribute__方法、gettotaldistance方法)、一个装饰器和一个property函数定义属性变量语句。
    在__ getattribute__方法中将所有访问实例属性的属性名输出,并调用父类object类的__ getattribute__方法返回真正的数据。
    装饰器将实例变量power定义为carpower属性输出,虽然二者的值是一样的,但是两个不同的实例属性;
    property函数定义了totaldist属性,该属性输出的是实例变量totaldistance的值,二者值相同但实际上也是不同实例变量。然后我们执行totaldist和carpower的读取,看看输出是怎样的?
  2. 案例代码
>>> class Car(): 
    def __init__(self, power):
        self.power = power
        self.totaldistance = 10
        
    def __getattribute__(self, properyname):
        print("In __getattribute__,you are getting  properyty: {}".format(properyname))
        return object.__getattribute__(self, properyname)  
    
    @property  
    def carpower(self): #定义carpower属性的getter只读装饰器函数
       print("execute carpower")
       return self.power
    
    def gettotaldistance(self):
        print("execute gettotaldistance")
        return self.totaldistance #定义property的fget函数
    totaldist = property(gettotaldistance,None,None,'车辆行驶总里程')  #定义totaldist属性

	      
>>> car = Car('汽油发动机')
>>> car.carpower  #获取装饰器@property 定义属性carpower
In __getattribute__,you are getting  properyty: carpower
execute carpower
In __getattribute__,you are getting  properyty: power
'汽油发动机'
>>> car.totaldist #获取内置函数property 定义属性totaldist 
In __getattribute__,you are getting  properyty: totaldist
execute gettotaldistance
In __getattribute__,you are getting  properyty: totaldistance
10
>>>
  1. 案例截屏
    在这里插入图片描述
  2. 案例说明
    通过以上案例可以看出,使用装饰器@property 或property函数定义的属性(下称”定义属性”)访问过程是这样的:
    1> 执行__ getattribute__方法去访问“定义属性”;
    2> 调用@property的getter 或property类型的fget去执行具体的访问方法;
    3> 访问方法访问具体实例变量数据,再次触发__ getattribute__方法返回真正的数据。

三、 总结
通过上述案例及分析, property的fget、@property装饰器getter两者是基本是相同的,只是两种不同的实现,为了描述方便,后面统称为“property的get方法”。
__getattribute__与“property的get方法”之间的异同点如下:

  1. 都能在对实例对象进行属性get时触发,但“property的get方法”只能针对一个“定义属性”,而__getattribute__能针对所有属性包括特殊变量进行触发,触发后参数中有具体属性名;
  2. “property的get方法”只能针对“定义属性”的实例变量,而__getattribute__还能针对实例方法;
  3. 两者在解释器使用dir、type、help访问属性时触发,只不过“property的get方法”只能针对“定义属性”触发,而__getattribute__针对实例方法和所有实例变量;
  4. “property的get方法”返回数据一般是对应方法直接访问实例(self.属性)变量,而__getattribute__不能直接访问实例(self.属性)变量,否则会触发递归调用,只能用父类的__getattribute__方法返回数据;
  5. “定义属性”由于是针对实例变量进行了查询或查询后经过运算返回的,因此它的访问首先会执行__getattribute__方法去访问“定义属性”的数据,然后该访问被定向到绑定的get方法,该get方法会再次触发__getattribute__方法去访问实际需要访问的实例变量,因此使用“property的get方法”去查询“定义属性”的数据时,肯定会触发至少两次__getattribute__方法的执行(次数需要看“property的get方法”去查询的实例变量有多少个);
  6. __getattribute__方法是截获所有通过“实例名.属性名”访问实例变量、类变量、实例方法的所有操作来控制对属性的访问,而“property的get方法”则是为了方便访问数据,提高代码的易读性而实施的属性和方法的绑定。

本节结合案例深入分析了__getattribute__方法与“property的get方法”的异同点,。
老猿Python,跟老猿学Python!
博客地址:https://blog.youkuaiyun.com/LaoYuanPython

请大家多多支持,点赞、评论和加关注!谢谢!

<think>好的,用户问的是Python中__get__、__getattr__和__getattribute__的区别。这个问题涉及到Python的描述符协议和属性访问机制,我得仔细理清楚每个方法的作用和调用时机。 首先,__get__是描述符协议的一部分。描述符通常用于管理类属性的访问,比如在@property装饰器背后就是用到了描述符。当一个类属性被定义为描述符时,每次访问该属性都会触发__get__方法。需要区分实例访问和类访问的不同情况,比如通过实例访问时,__get__的参数是实例和类,而通过类访问时,实例参数为None。这点需要注意。 接下来是__getattr__。这个方法在属性查找失败时被调用。也就是说,当访问一个不存在的属性时,Python会调用__getattr__。这个方法适合用来实现动态属性或者延迟加载。例如,如果用户访问obj.undefined_attr,而该属性不存在,就会触发__getattr__。需要注意的是,如果属性存在于实例或类中,这个方法不会被调用。 然后是__getattribute__。这个方法在每次访问属性时都会被调用,无论属性是否存在。它拦截所有属性访问,因此在使用时需要特别小心,避免无限递归。通常,在覆盖__getattribute__时,应该通过调用object类的__getattribute__来获取属性,比如使用object.__getattribute__(self, name)。 用户可能需要知道这些方法的调用顺序和适用场景。比如,__getattribute__总是被调用,如果在这里抛出AttributeError,才会触发__getattr__。而__get__则是在属性被访问时,如果该属性是一个描述符实例,才会被调用。需要举一些例子来说明不同的情况,比如使用描述符的类,或者动态生成属性的情况。 还需要注意常见的错误,比如在__getattribute__中直接访问self.name会导致递归调用,必须通过object.__getattribute__来避免。同时,__getattr__只有在属性不存在时才会被调用,所以它适合处理缺失属性的情况。 另外,用户可能在理解这三者的区别时容易混淆,特别是__get__和另外两个方法,因为它们属于不同的机制。需要明确__get__属于描述符协议,用于管理类属性的访问,而__getattr__和__getattribute__属于实例的属性访问机制。 总结时,要分点说明每个方法的作用、调用时机和典型应用场景,帮助用户清晰区分。同时,提供示例代码会更有助于理解,比如定义一个描述符类,展示__get__的使用,或者覆盖__getattr__来动态生成属性。 可能还需要回答相关问题,比如如何正确使用这些方法,避免常见错误,或者它们在实际项目中的应用案例。比如,Django模型中的字段可能使用描述符来管理数据库访问,或者在动态代理中使用__getattr__来转发方法调用。 最后,确保回答结构清晰,逐步解释每个方法,并对比它们的差异,帮助用户逐步建立正确的理解。</think>### Python中__get__、__getattr__和__getattribute__的区别 #### 1. **`__get__`:描述符协议** - **作用**:用于实现**描述符协议**,主要用于类属性级别的访问控制。当某个类属性被定义为描述符时(例如`@property`装饰器底层实现),访问该属性会触发`__get__`方法。 - **调用时机**:当通过实例或类访问描述符属性时触发。 - **参数**:`def __get__(self, instance, owner)`,其中`instance`是实例对象,`owner`是拥有该属性的类。 - **示例**: ```python class Descriptor: def __get__(self, instance, owner): print("调用__get__") return "值" class MyClass: attr = Descriptor() obj = MyClass() print(obj.attr) # 输出 "调用__get__" 后返回 "值" ``` #### 2. **`__getattr__`:属性缺失处理** - **作用**:在**属性查找失败时触发**,用于动态处理不存在的属性。 - **调用时机**:当访问的属性不存在于实例或类中时触发。 - **参数**:`def __getattr__(self, name)`,`name`为属性名。 - **典型场景**:动态生成属性、延迟加载。 - **示例**: ```python class DynamicAttributes: def __getattr__(self, name): print(f"动态生成属性 {name}") setattr(self, name, "默认值") return "默认值" obj = DynamicAttributes() print(obj.new_attr) # 输出 "动态生成属性 new_attr" 后返回 "默认值" ``` #### 3. **`__getattribute__`:全属性拦截** - **作用**:**拦截所有属性访问**,无论属性是否存在。 - **调用时机**:每次访问属性时触发(包括已存在的属性)。 - **参数**:`def __getattribute__(self, name)`。 - **注意事项**: - 必须通过`object.__getattribute__(self, name)`访问属性,否则会陷入无限递归。 - 覆盖此方法需谨慎,可能显著影响性能。 - **示例**: ```python class Logger: def __getattribute__(self, name): print(f"访问属性 {name}") return object.__getattribute__(self, name) obj = Logger() obj.x = 10 print(obj.x) # 输出 "访问属性 x" 后返回 10 ``` --- ### 对比总结 | 方法 | 触发条件 | 用途 | 优先级 | |---------------------|----------------------------|--------------------------------------|---------------| | `__get__` | 访问描述符类属性时 | 实现属性计算、验证 | 描述符协议优先 | | `__getattr__` | 属性不存在时 | 动态属性处理、错误兜底 | 次优先级 | | `__getattribute__` | 所有属性访问 | 全局属性控制、日志记录 | 最高优先级 | --- ### 常见问题 #### 1. **如何避免`__getattribute__`中的无限递归?** - 必须使用`object.__getattribute__(self, name)`访问属性,而非直接`self.name`。例如: ```python def __getattribute__(self, name): # 错误写法:return self.__dict__[name] → 递归调用 return object.__getattribute__(self, name) # 正确 ``` #### 2. **`__getattr__`和`__getattribute__`的调用顺序?** - 若`__getattribute__`抛出`AttributeError`,则触发`__getattr__`[^1]。 #### 3. **描述符的实际应用场景?** - 例如在Django模型中,字段通过描述符实现数据库字段Python属性的映射: ```python class Field: def __get__(self, instance, owner): return instance.__dict__[self.name] ``` ---
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LaoYuanPython

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值